Compare commits
128 Commits
release/24
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90829e4959 | ||
|
|
6a1ff37011 | ||
|
|
aa68369f95 | ||
|
|
7837364b86 | ||
|
|
31f0e39617 | ||
|
|
a48151920d | ||
|
|
8199653ddd | ||
|
|
9d650519c7 | ||
|
|
a545eaca97 | ||
|
|
9da9de2d2d | ||
|
|
c9ddd9d513 | ||
|
|
415cfd57e7 | ||
|
|
b1ede93cdb | ||
|
|
32df465c8a | ||
|
|
2f1c5df11d | ||
|
|
f85f6a7fdc | ||
|
|
93cc80a4b3 | ||
|
|
0f3e401d42 | ||
|
|
a9f2c33053 | ||
|
|
c5ef8c3fec | ||
|
|
ad696bd60b | ||
|
|
9da7462f3e | ||
|
|
0ecdf354c6 | ||
|
|
67f10632fb | ||
|
|
044b910357 | ||
|
|
3de943e50f | ||
|
|
1a2272249d | ||
|
|
0dfeec6cae | ||
|
|
8d193021ab | ||
|
|
f45226a680 | ||
|
|
40d55805ff | ||
|
|
7d6d0b012b | ||
|
|
d7d2c6bf01 | ||
|
|
ce1c6096d3 | ||
|
|
8c4ea6180a | ||
|
|
95649ba758 | ||
|
|
2b61f2a9e8 | ||
|
|
47242aa66c | ||
|
|
31a8abe615 | ||
|
|
4dae3c34b4 | ||
|
|
65aa8919b9 | ||
|
|
acf8eac0d4 | ||
|
|
e8aa29d3c2 | ||
|
|
a36194afe2 | ||
|
|
8554898e45 | ||
|
|
55b29f645b | ||
|
|
52cc91ba3e | ||
|
|
c9e3a9f8fe | ||
|
|
c3de788956 | ||
|
|
53e3c36f7e | ||
|
|
20908107a0 | ||
|
|
cbe1a0d6f8 | ||
|
|
7d8c79ec57 | ||
|
|
9bb89c728f | ||
|
|
be2e28cc53 | ||
|
|
57be57971d | ||
|
|
64eed47f04 | ||
|
|
7659105fda | ||
|
|
18c9376992 | ||
|
|
3a4aca7fbd | ||
|
|
85ff8cdd4a | ||
|
|
b1048cb84a | ||
|
|
ff9cf9abf6 | ||
|
|
157c84663d | ||
|
|
908e4fb5a4 | ||
|
|
fdca7d58e5 | ||
|
|
2eb26d512b | ||
|
|
b7fdf7d60f | ||
|
|
2a1e66468d | ||
|
|
cd7faf751e | ||
|
|
d79d806cda | ||
|
|
cae389c04c | ||
|
|
4e6850a60c | ||
|
|
a71f8cf358 | ||
|
|
fe6ff9b2b6 | ||
|
|
5a0f3e474d | ||
|
|
cd9c4239b1 | ||
|
|
0cf1d87b12 | ||
|
|
99d91c1e07 | ||
|
|
457f80bc9b | ||
|
|
968e3fc23b | ||
|
|
b1e338579d | ||
|
|
7e4361bb5e | ||
|
|
dd35fa3d91 | ||
|
|
affb911d97 | ||
|
|
785f9cd1b6 | ||
|
|
419056f098 | ||
|
|
396db18de2 | ||
|
|
f124721cd9 | ||
|
|
52e49b49fb | ||
|
|
10e03777e9 | ||
|
|
27538b72e9 | ||
|
|
19ace37a75 | ||
|
|
90c5377ef0 | ||
|
|
f0e3d3e474 | ||
|
|
26064ebf8c | ||
|
|
1d92b74131 | ||
|
|
5f83c137d2 | ||
|
|
fa57db8e83 | ||
|
|
9d3c2e19f5 | ||
|
|
46a9600d55 | ||
|
|
de40701cf6 | ||
|
|
307536c6b6 | ||
|
|
203be8bd35 | ||
|
|
1e644587b3 | ||
|
|
66a60f09e3 | ||
|
|
69b6f16ec1 | ||
|
|
10b4274a3d | ||
|
|
28c9d94457 | ||
|
|
d74fd1a560 | ||
|
|
624b1b06c5 | ||
|
|
95376c2ccc | ||
|
|
1eb622165b | ||
|
|
9d6ba324fb | ||
|
|
d462852ab9 | ||
|
|
eeec81898b | ||
|
|
ab0c8b8170 | ||
|
|
91d295e0bb | ||
|
|
125974dd7a | ||
|
|
92895a7d00 | ||
|
|
d9308440e6 | ||
|
|
5340142c06 | ||
|
|
012d30ee9f | ||
|
|
031d69d996 | ||
|
|
8b63c18f65 | ||
|
|
dc2f11eb2b | ||
|
|
13e64a9487 | ||
|
|
6a3c88ae5b |
@@ -40,4 +40,4 @@ Dependencies:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
per-test-timeout: 90
|
per-test-timeout: 90
|
||||||
require-passing-tests-on: [ 'Linux', 'Android', 'FreeBSD' ]
|
require-passing-tests-on: [ '@all' ]
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
|
|
||||||
# KDE Applications version, managed by release script.
|
# KDE Applications version, managed by release script.
|
||||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||||
set(RELEASE_SERVICE_VERSION_MINOR "05")
|
set(RELEASE_SERVICE_VERSION_MINOR "07")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "2")
|
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||||
|
|
||||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||||
|
|||||||
56
appiumtests/createroomtest.py
Executable file
56
appiumtests/createroomtest.py
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
|
||||||
|
from appium import webdriver
|
||||||
|
from appium.options.common.base import AppiumOptions
|
||||||
|
from appium.webdriver.common.appiumby import AppiumBy
|
||||||
|
|
||||||
|
|
||||||
|
class CreateRoomTest(unittest.TestCase):
|
||||||
|
|
||||||
|
mockServerProcess: subprocess.Popen
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
|
||||||
|
options = AppiumOptions()
|
||||||
|
options.set_capability("app", "neochat --ignore-ssl-errors --test")
|
||||||
|
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if not self._outcome.result.wasSuccessful():
|
||||||
|
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(self):
|
||||||
|
self.mockServerProcess.terminate()
|
||||||
|
self.driver.quit()
|
||||||
|
|
||||||
|
def test_create_room(self):
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Show Menu").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Create a Room").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Name:").send_keys("Super awesome room name")#
|
||||||
|
time.sleep(0.1) # without this, the second half of the text is sent to the topic field?!
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Topic:").send_keys("There are not enough raccoons here")
|
||||||
|
time.sleep(0.1)
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Create Room").click()
|
||||||
|
time.sleep(0.1)
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Super awesome room name").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="Show Room Information").click()
|
||||||
|
self.driver.find_element(by=AppiumBy.NAME, value="There are not enough raccoons here")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
78
appiumtests/data/sync_response_new_room.json
Normal file
78
appiumtests/data/sync_response_new_room.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"next_batch": "batch1234",
|
||||||
|
"rooms": {
|
||||||
|
"join": {
|
||||||
|
"!newroom123321:localhost:1234": {
|
||||||
|
"state": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"type": "m.room.member",
|
||||||
|
"state_key": "@user:localhost:1234",
|
||||||
|
"sender": "@user:localhost:1234",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"event_id": "$event_id_1234_0:localhost:1234",
|
||||||
|
"room_id": "!newroom123321:localhost:1234",
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "",
|
||||||
|
"displayname": "A Display Name",
|
||||||
|
"membership": "join",
|
||||||
|
"reason": "Nothing"
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.room.name",
|
||||||
|
"state_key": "",
|
||||||
|
"sender": "@user:localhost:1234",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"event_id": "$event_id_1234_1:localhost:1234",
|
||||||
|
"room_id": "!newroom123321:localhost:1234",
|
||||||
|
"content": {
|
||||||
|
"name": "Super awesome room name"
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.room.topic",
|
||||||
|
"state_key": "",
|
||||||
|
"sender": "@user:localhost:1234",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"event_id": "$event_id_1234_2:localhost:1234",
|
||||||
|
"room_id": "!newroom123321:localhost:1234",
|
||||||
|
"content": {
|
||||||
|
"topic": "There are not enough raccoons here"
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"type": "m.room.message",
|
||||||
|
"sender": "@user:localhost:1234",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"event_id": "$event_id_1234_1:localhost:1234",
|
||||||
|
"room_id": "!newroom123321:localhost:1234",
|
||||||
|
"content": {
|
||||||
|
"body": "This is a message",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<a href=\"https://matrix.to/#/@user:localhost:1234\">User</a>:",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ from flask import Flask, request, abort
|
|||||||
import os
|
import os
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
next_sync_payload = ""
|
||||||
|
|
||||||
|
|
||||||
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
||||||
def login_get():
|
def login_get():
|
||||||
@@ -42,8 +44,13 @@ def load_json(name):
|
|||||||
|
|
||||||
@app.route("/_matrix/client/r0/sync")
|
@app.route("/_matrix/client/r0/sync")
|
||||||
def sync():
|
def sync():
|
||||||
|
global next_sync_payload
|
||||||
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
|
result = dict()
|
||||||
|
if len(next_sync_payload) > 0:
|
||||||
|
result = load_json(next_sync_payload)
|
||||||
|
next_sync_payload = ""
|
||||||
|
else:
|
||||||
|
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@app.route("/.well-known/matrix/client")
|
@app.route("/.well-known/matrix/client")
|
||||||
@@ -65,6 +72,18 @@ def upload_keys():
|
|||||||
reply = dict()
|
reply = dict()
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
@app.route("/_matrix/client/v3/createRoom", methods=["POST"])
|
||||||
|
def create_room():
|
||||||
|
global next_sync_payload
|
||||||
|
data = request.get_json()
|
||||||
|
if data["name"] != "Super awesome room name" or data["topic"] != "There are not enough raccoons here":
|
||||||
|
return dict(), 400
|
||||||
|
response = dict()
|
||||||
|
response["room_id"] = "!newroom123321:localhost:1234"
|
||||||
|
next_sync_payload = "sync_response_new_room"
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(ssl_context='adhoc', port=1234)
|
app.run(ssl_context='adhoc', port=1234)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "https://matrix.to/#/@alice:example.org",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$validlink1:example.org",
|
|
||||||
"origin_server_ts": 1432735824654,
|
|
||||||
"room_id": "!test:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "mxc://example.org/SEsfnsuifSDFSSEF",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$validlink1:example.org",
|
|
||||||
"origin_server_ts": 1432735824654,
|
|
||||||
"room_id": "!test:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "testhttps://kde.org",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$validlink1:example.org",
|
|
||||||
"origin_server_ts": 1432735824654,
|
|
||||||
"room_id": "!test:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "https://kde.org",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$validlink1:example.org",
|
|
||||||
"origin_server_ts": 1432735824654,
|
|
||||||
"room_id": "!test:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "www.example.org",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$validlink1:example.org",
|
|
||||||
"origin_server_ts": 1432735824654,
|
|
||||||
"room_id": "!test:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "[Rich Link](https://kde.org)",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$validlink1:example.org",
|
|
||||||
"origin_server_ts": 1432735824654,
|
|
||||||
"room_id": "!test:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -56,6 +56,7 @@ private Q_SLOTS:
|
|||||||
void genericBody();
|
void genericBody();
|
||||||
void nullGenericBody();
|
void nullGenericBody();
|
||||||
void markdownBody();
|
void markdownBody();
|
||||||
|
void markdownBodyReply();
|
||||||
void subtitle();
|
void subtitle();
|
||||||
void nullSubtitle();
|
void nullSubtitle();
|
||||||
void mediaInfo();
|
void mediaInfo();
|
||||||
@@ -301,6 +302,13 @@ void EventHandlerTest::markdownBody()
|
|||||||
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
|
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EventHandlerTest::markdownBodyReply()
|
||||||
|
{
|
||||||
|
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||||
|
|
||||||
|
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
|
||||||
|
}
|
||||||
|
|
||||||
void EventHandlerTest::subtitle()
|
void EventHandlerTest::subtitle()
|
||||||
{
|
{
|
||||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||||
|
|||||||
@@ -6,12 +6,11 @@
|
|||||||
|
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
#include <Quotient/quotient_common.h>
|
#include <Quotient/quotient_common.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
#include "testutils.h"
|
#include "testutils.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -30,6 +29,9 @@ private Q_SLOTS:
|
|||||||
void linkPreviewsMatch_data();
|
void linkPreviewsMatch_data();
|
||||||
void linkPreviewsMatch();
|
void linkPreviewsMatch();
|
||||||
|
|
||||||
|
void multipleLinkPreviewsMatch_data();
|
||||||
|
void multipleLinkPreviewsMatch();
|
||||||
|
|
||||||
void linkPreviewsReject_data();
|
void linkPreviewsReject_data();
|
||||||
void linkPreviewsReject();
|
void linkPreviewsReject();
|
||||||
};
|
};
|
||||||
@@ -42,45 +44,59 @@ void LinkPreviewerTest::initTestCase()
|
|||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsMatch_data()
|
void LinkPreviewerTest::linkPreviewsMatch_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QString>("eventSource");
|
QTest::addColumn<QString>("inputString");
|
||||||
QTest::addColumn<QUrl>("testOutputLink");
|
QTest::addColumn<QUrl>("testOutputLink");
|
||||||
|
|
||||||
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
|
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QUrl("https://kde.org"_ls);
|
||||||
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
|
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QUrl("https://kde.org"_ls);
|
||||||
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
|
QTest::newRow("richHttpsLinkDescription") << QStringLiteral("<a href=\"https://kde.org\">https://kde.org</a>") << QUrl("https://kde.org"_ls);
|
||||||
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsMatch()
|
void LinkPreviewerTest::linkPreviewsMatch()
|
||||||
{
|
{
|
||||||
QFETCH(QString, eventSource);
|
QFETCH(QString, inputString);
|
||||||
QFETCH(QUrl, testOutputLink);
|
QFETCH(QUrl, testOutputLink);
|
||||||
|
|
||||||
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
|
auto link = LinkPreviewer::linkPreviews(inputString)[0];
|
||||||
auto linkPreviewer = LinkPreviewer(LinkPreviewer::linkPreview(event.get()), connection);
|
|
||||||
|
|
||||||
QCOMPARE(linkPreviewer.empty(), false);
|
QCOMPARE(link, testOutputLink);
|
||||||
QCOMPARE(linkPreviewer.url(), testOutputLink);
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::multipleLinkPreviewsMatch_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("inputString");
|
||||||
|
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||||
|
|
||||||
|
QTest::newRow("multipleHttps") << QStringLiteral("www.example.org https://kde.org") << QList{QUrl("www.example.org"_ls), QUrl("https://kde.org"_ls)};
|
||||||
|
QTest::newRow("multipleHttps1Invalid") << QStringLiteral("www.example.org mxc://example.org/SEsfnsuifSDFSSEF") << QList{QUrl("www.example.org"_ls)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::multipleLinkPreviewsMatch()
|
||||||
|
{
|
||||||
|
QFETCH(QString, inputString);
|
||||||
|
QFETCH(QList<QUrl>, testOutputLinks);
|
||||||
|
|
||||||
|
auto links = LinkPreviewer::linkPreviews(inputString);
|
||||||
|
|
||||||
|
QCOMPARE(links, testOutputLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsReject_data()
|
void LinkPreviewerTest::linkPreviewsReject_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QString>("eventSource");
|
QTest::addColumn<QString>("inputString");
|
||||||
|
|
||||||
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
|
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF");
|
||||||
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
|
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org");
|
||||||
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
|
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org");
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsReject()
|
void LinkPreviewerTest::linkPreviewsReject()
|
||||||
{
|
{
|
||||||
QFETCH(QString, eventSource);
|
QFETCH(QString, inputString);
|
||||||
|
|
||||||
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
|
auto links = LinkPreviewer::linkPreviews(inputString);
|
||||||
auto linkPreviewer = LinkPreviewer(LinkPreviewer::linkPreview(event.get()), connection);
|
|
||||||
|
|
||||||
QCOMPARE(linkPreviewer.empty(), true);
|
QCOMPARE(links.empty(), true);
|
||||||
QCOMPARE(linkPreviewer.url(), QUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QTEST_MAIN(LinkPreviewerTest)
|
QTEST_MAIN(LinkPreviewerTest)
|
||||||
|
|||||||
@@ -80,14 +80,12 @@
|
|||||||
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
|
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
|
||||||
<description>
|
<description>
|
||||||
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
||||||
<p xml:lang="ar">نيوتشات هو تطبيق دردشة يتيح لك الاستفادة الكاملة من شبكة Matrix. فهو يوفر لك طريقة آمنة لإرسال الرسائل النصية ومقاطع الفيديو والملفات الصوتية إلى عائلتك وزملائك وأصدقائك.</p>
|
|
||||||
<p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
|
<p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
|
||||||
<p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
|
<p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
|
||||||
<p xml:lang="en-GB">NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
<p xml:lang="en-GB">NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
||||||
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
|
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
|
||||||
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
|
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
|
||||||
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
|
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
|
||||||
<p xml:lang="fi">NeoChat on keskustelusovellus, jolla Matrix-verkosta saa täyden hyödyn. Se tarjoaa salatun kanavan lähettää perheelle, työkavereille ja ystäville tekstiviestejä sekä video- ja äänitiedostoja.</p>
|
|
||||||
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
|
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
|
||||||
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
|
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
|
||||||
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
|
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
|
||||||
@@ -95,10 +93,8 @@
|
|||||||
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
|
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
|
||||||
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
|
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
|
||||||
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
||||||
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
|
||||||
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
||||||
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
||||||
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
|
|
||||||
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
|
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
|
||||||
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
|
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
|
||||||
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
|
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
|
||||||
@@ -115,7 +111,7 @@
|
|||||||
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
|
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
|
||||||
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
|
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
|
||||||
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
|
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
|
||||||
<p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
|
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
|
||||||
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
||||||
<p xml:lang="ka">NeoChat მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
<p xml:lang="ka">NeoChat მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
||||||
<p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p>
|
<p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p>
|
||||||
@@ -214,7 +210,7 @@
|
|||||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||||
<li xml:lang="sv">Sticker Packs - MSC2545</li>
|
<li xml:lang="sv">Sticker Packs - MSC2545</li>
|
||||||
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
|
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
|
||||||
<li xml:lang="tr">Çıkartma Paketleri — MSC2545</li>
|
<li xml:lang="tr">Yapışkan Paketleri — MSC2545</li>
|
||||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||||
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
|
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
|
||||||
@@ -314,14 +310,12 @@
|
|||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<image>https://cdn.kde.org/screenshots/neochat/spaces.png</image>
|
<image>https://cdn.kde.org/screenshots/neochat/spaces.png</image>
|
||||||
<caption>Discover new communities with Matrix Spaces</caption>
|
<caption>Discover new communities with Matrix Spaces</caption>
|
||||||
<caption xml:lang="ar">اكتشف مجتمعات جديدة مع فضاءات ماتركس</caption>
|
|
||||||
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption>
|
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption>
|
||||||
<caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption>
|
<caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption>
|
||||||
<caption xml:lang="en-GB">Discover new communities with Matrix Spaces</caption>
|
<caption xml:lang="en-GB">Discover new communities with Matrix Spaces</caption>
|
||||||
<caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption>
|
<caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption>
|
||||||
<caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption>
|
<caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption>
|
||||||
<caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption>
|
<caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption>
|
||||||
<caption xml:lang="fi">Löydä uusia yhteisöjä Matrix Spacesillä</caption>
|
|
||||||
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
|
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
|
||||||
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption>
|
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption>
|
||||||
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
|
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
|
||||||
@@ -329,10 +323,8 @@
|
|||||||
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
|
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
|
||||||
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
|
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
|
||||||
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
||||||
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
|
||||||
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
||||||
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
||||||
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption>
|
|
||||||
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
|
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
|
||||||
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
|
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
|
||||||
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
|
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
|
||||||
@@ -415,8 +407,6 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="24.05.2" date="2024-07-04"/>
|
|
||||||
<release version="24.05.1" date="2024-06-13"/>
|
|
||||||
<release version="24.05.0" date="2024-05-23"/>
|
<release version="24.05.0" date="2024-05-23"/>
|
||||||
<release version="24.02.2" date="2024-04-11"/>
|
<release version="24.02.2" date="2024-04-11"/>
|
||||||
<release version="24.02.1" date="2024-03-21"/>
|
<release version="24.02.1" date="2024-03-21"/>
|
||||||
|
|||||||
2362
po/ar/neochat.po
2362
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1184
po/ast/neochat.po
1184
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1355
po/az/neochat.po
1355
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||||||
></term>
|
></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para
|
<para
|
||||||
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:example.org o matrix:r/root:example.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
|
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:exemple.org o matrix:r/root:exemple.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|||||||
1348
po/ca/neochat.po
1348
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1288
po/cs/neochat.po
1288
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1274
po/da/neochat.po
1274
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1343
po/de/neochat.po
1343
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1364
po/el/neochat.po
1364
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1307
po/en_GB/neochat.po
1307
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1355
po/eo/neochat.po
1355
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1303
po/es/neochat.po
1303
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1398
po/eu/neochat.po
1398
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1457
po/fi/neochat.po
1457
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1310
po/fr/neochat.po
1310
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1349
po/hu/neochat.po
1349
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1306
po/ia/neochat.po
1306
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1351
po/id/neochat.po
1351
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1303
po/ie/neochat.po
1303
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1306
po/it/neochat.po
1306
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1184
po/ja/neochat.po
1184
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1300
po/ka/neochat.po
1300
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1336
po/ko/neochat.po
1336
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1184
po/lt/neochat.po
1184
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1331
po/lv/neochat.po
1331
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1337
po/nl/neochat.po
1337
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2265
po/nn/neochat.po
2265
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1339
po/pa/neochat.po
1339
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1326
po/pl/neochat.po
1326
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1338
po/pt/neochat.po
1338
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1355
po/pt_BR/neochat.po
1355
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1348
po/ru/neochat.po
1348
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1348
po/sk/neochat.po
1348
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1287
po/sl/neochat.po
1287
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
2210
po/sv/neochat.po
2210
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1300
po/ta/neochat.po
1300
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1259
po/tok/neochat.po
1259
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1361
po/tr/neochat.po
1361
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1317
po/uk/neochat.po
1317
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1264
po/zh_CN/neochat.po
1264
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1279
po/zh_TW/neochat.po
1279
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,6 @@ add_library(neochat STATIC
|
|||||||
models/customemojimodel.h
|
models/customemojimodel.h
|
||||||
clipboard.cpp
|
clipboard.cpp
|
||||||
clipboard.h
|
clipboard.h
|
||||||
matriximageprovider.cpp
|
|
||||||
matriximageprovider.h
|
|
||||||
models/messageeventmodel.cpp
|
models/messageeventmodel.cpp
|
||||||
models/messageeventmodel.h
|
models/messageeventmodel.h
|
||||||
models/messagefiltermodel.cpp
|
models/messagefiltermodel.cpp
|
||||||
@@ -177,6 +175,16 @@ add_library(neochat STATIC
|
|||||||
foreigntypes.h
|
foreigntypes.h
|
||||||
models/threepidmodel.cpp
|
models/threepidmodel.cpp
|
||||||
models/threepidmodel.h
|
models/threepidmodel.h
|
||||||
|
threepidaddhelper.cpp
|
||||||
|
threepidaddhelper.h
|
||||||
|
jobs/neochatadd3pidjob.cpp
|
||||||
|
jobs/neochatadd3pidjob.h
|
||||||
|
identityserverhelper.cpp
|
||||||
|
identityserverhelper.h
|
||||||
|
enums/powerlevel.cpp
|
||||||
|
enums/powerlevel.h
|
||||||
|
models/permissionsmodel.cpp
|
||||||
|
models/permissionsmodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||||
@@ -207,16 +215,10 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/TypingPane.qml
|
qml/TypingPane.qml
|
||||||
qml/QuickSwitcher.qml
|
qml/QuickSwitcher.qml
|
||||||
qml/HoverActions.qml
|
qml/HoverActions.qml
|
||||||
qml/ChatBar.qml
|
|
||||||
qml/AttachmentPane.qml
|
qml/AttachmentPane.qml
|
||||||
qml/ReplyPane.qml
|
|
||||||
qml/CompletionMenu.qml
|
|
||||||
qml/PieProgressBar.qml
|
|
||||||
qml/QuickFormatBar.qml
|
qml/QuickFormatBar.qml
|
||||||
qml/EmojiPicker.qml
|
|
||||||
qml/UserDetailDialog.qml
|
qml/UserDetailDialog.qml
|
||||||
qml/CreateRoomDialog.qml
|
qml/CreateRoomDialog.qml
|
||||||
qml/EmojiDialog.qml
|
|
||||||
qml/OpenFileDialog.qml
|
qml/OpenFileDialog.qml
|
||||||
qml/KeyVerificationDialog.qml
|
qml/KeyVerificationDialog.qml
|
||||||
qml/ConfirmLogoutDialog.qml
|
qml/ConfirmLogoutDialog.qml
|
||||||
@@ -236,9 +238,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/ConfirmEncryptionDialog.qml
|
qml/ConfirmEncryptionDialog.qml
|
||||||
qml/RemoveSheet.qml
|
qml/RemoveSheet.qml
|
||||||
qml/BanSheet.qml
|
qml/BanSheet.qml
|
||||||
qml/EmojiTonesPicker.qml
|
|
||||||
qml/EmojiDelegate.qml
|
|
||||||
qml/EmojiGrid.qml
|
|
||||||
qml/RoomSearchPage.qml
|
qml/RoomSearchPage.qml
|
||||||
qml/LocationChooser.qml
|
qml/LocationChooser.qml
|
||||||
qml/TimelineView.qml
|
qml/TimelineView.qml
|
||||||
@@ -262,7 +261,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/SelectParentDialog.qml
|
qml/SelectParentDialog.qml
|
||||||
qml/QrCodeMaximizeComponent.qml
|
qml/QrCodeMaximizeComponent.qml
|
||||||
qml/SelectSpacesDialog.qml
|
qml/SelectSpacesDialog.qml
|
||||||
qml/AttachDialog.qml
|
|
||||||
qml/NotificationsView.qml
|
qml/NotificationsView.qml
|
||||||
qml/SearchPage.qml
|
qml/SearchPage.qml
|
||||||
qml/ServerComboBox.qml
|
qml/ServerComboBox.qml
|
||||||
@@ -280,12 +278,16 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/ConfirmLeaveDialog.qml
|
qml/ConfirmLeaveDialog.qml
|
||||||
qml/CodeMaximizeComponent.qml
|
qml/CodeMaximizeComponent.qml
|
||||||
qml/EditStateDialog.qml
|
qml/EditStateDialog.qml
|
||||||
|
qml/ConsentDialog.qml
|
||||||
|
qml/AskDirectChatConfirmation.qml
|
||||||
|
qml/HoverLinkIndicator.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(timeline)
|
add_subdirectory(timeline)
|
||||||
add_subdirectory(devtools)
|
add_subdirectory(devtools)
|
||||||
add_subdirectory(login)
|
add_subdirectory(login)
|
||||||
|
add_subdirectory(chatbar)
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||||
@@ -379,7 +381,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
|
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
|
||||||
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin)
|
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin)
|
||||||
target_link_libraries(neochat PUBLIC
|
target_link_libraries(neochat PUBLIC
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
|
|||||||
@@ -13,15 +13,13 @@ import org.kde.neochat
|
|||||||
QQC2.Popup {
|
QQC2.Popup {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
padding: 16
|
||||||
|
|
||||||
signal chosen(string path)
|
signal chosen(string path)
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
|
Layout.preferredWidth: 160
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
icon.name: 'mail-attachment'
|
icon.name: 'mail-attachment'
|
||||||
@@ -39,8 +37,11 @@ QQC2.Popup {
|
|||||||
Kirigami.Separator {}
|
Kirigami.Separator {}
|
||||||
|
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
|
Layout.preferredWidth: 160
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
padding: 16
|
||||||
|
|
||||||
icon.name: 'insert-image'
|
icon.name: 'insert-image'
|
||||||
text: i18n("Clipboard image")
|
text: i18n("Clipboard image")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
19
src/chatbar/CMakeLists.txt
Normal file
19
src/chatbar/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
qt_add_library(chatbar STATIC)
|
||||||
|
qt_add_qml_module(chatbar
|
||||||
|
URI org.kde.neochat.chatbar
|
||||||
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar
|
||||||
|
QML_FILES
|
||||||
|
AttachDialog.qml
|
||||||
|
ChatBar.qml
|
||||||
|
CompletionMenu.qml
|
||||||
|
EmojiDelegate.qml
|
||||||
|
EmojiGrid.qml
|
||||||
|
ReplyPane.qml
|
||||||
|
PieProgressBar.qml
|
||||||
|
EmojiPicker.qml
|
||||||
|
EmojiDialog.qml
|
||||||
|
EmojiTonesPicker.qml
|
||||||
|
)
|
||||||
@@ -24,7 +24,7 @@ QQC2.ItemDelegate {
|
|||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !root.emoji.startsWith("image") && !root.isImage
|
visible: !root.emoji.startsWith("mxc") && !root.isImage
|
||||||
text: root.emoji
|
text: root.emoji
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
@@ -41,7 +41,7 @@ QQC2.ItemDelegate {
|
|||||||
}
|
}
|
||||||
Image {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: root.emoji.startsWith("image") || root.isImage
|
visible: root.emoji.startsWith("mxc") || root.isImage
|
||||||
source: visible ? root.emoji : ""
|
source: visible ? root.emoji : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,18 +38,22 @@ QQC2.Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
background: Kirigami.ShadowedRectangle {
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
radius: Kirigami.Units.cornerRadius
|
||||||
shadow {
|
color: Kirigami.Theme.backgroundColor
|
||||||
size: Kirigami.Units.largeSpacing
|
|
||||||
color: Qt.rgba(0.0, 0.0, 0.0, 0.3)
|
|
||||||
yOffset: 2
|
|
||||||
}
|
|
||||||
border {
|
border {
|
||||||
color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
width: 1
|
||||||
width: 2
|
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shadow {
|
||||||
|
size: Kirigami.Units.gridUnit
|
||||||
|
yOffset: 0
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
}
|
}
|
||||||
|
|
||||||
modal: true
|
modal: true
|
||||||
@@ -9,27 +9,20 @@
|
|||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QNetworkProxy>
|
|
||||||
#include <QQuickTextDocument>
|
|
||||||
#include <QQuickWindow>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QStringBuilder>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#include <Quotient/accountregistry.h>
|
#include <Quotient/accountregistry.h>
|
||||||
#include <Quotient/csapi/logout.h>
|
|
||||||
#include <Quotient/csapi/notifications.h>
|
#include <Quotient/csapi/notifications.h>
|
||||||
#include <Quotient/eventstats.h>
|
|
||||||
#include <Quotient/qt_connection_util.h>
|
#include <Quotient/qt_connection_util.h>
|
||||||
|
#include <Quotient/settings.h>
|
||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#include "proxycontroller.h"
|
#include "proxycontroller.h"
|
||||||
#include "roommanager.h"
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||||
#include "trayicon.h"
|
#include "trayicon.h"
|
||||||
@@ -45,6 +38,10 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
|
#include <kunifiedpush/connector.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
bool testMode = false;
|
bool testMode = false;
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -189,7 +186,7 @@ void Controller::invokeLogin()
|
|||||||
m_accountsLoading += accountId;
|
m_accountsLoading += accountId;
|
||||||
Q_EMIT accountsLoadingChanged();
|
Q_EMIT accountsLoadingChanged();
|
||||||
if (!account.homeserver().isEmpty()) {
|
if (!account.homeserver().isEmpty()) {
|
||||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
|
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
|
||||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
||||||
AccountSettings account{accountId};
|
AccountSettings account{accountId};
|
||||||
QString accessToken;
|
QString accessToken;
|
||||||
@@ -217,11 +214,11 @@ void Controller::invokeLogin()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
|
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QString &userId)
|
||||||
{
|
{
|
||||||
qDebug() << "Reading access token from the keychain for" << account.userId();
|
qDebug() << "Reading access token from the keychain for" << userId;
|
||||||
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
|
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
|
||||||
job->setKey(account.userId());
|
job->setKey(userId);
|
||||||
|
|
||||||
// Handling of errors
|
// Handling of errors
|
||||||
connect(job, &QKeychain::Job::finished, this, [this, job]() {
|
connect(job, &QKeychain::Job::finished, this, [this, job]() {
|
||||||
@@ -252,12 +249,12 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
|
|||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
|
bool Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
|
||||||
{
|
{
|
||||||
qDebug() << "Save the access token to the keychain for " << account.userId();
|
qDebug() << "Save the access token to the keychain for " << userId;
|
||||||
QKeychain::WritePasswordJob job(qAppName());
|
QKeychain::WritePasswordJob job(qAppName());
|
||||||
job.setAutoDelete(false);
|
job.setAutoDelete(false);
|
||||||
job.setKey(account.userId());
|
job.setKey(userId);
|
||||||
job.setBinaryData(accessToken);
|
job.setBinaryData(accessToken);
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||||
|
|||||||
@@ -5,15 +5,9 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include <Quotient/accountregistry.h>
|
#include <Quotient/accountregistry.h>
|
||||||
#include <Quotient/settings.h>
|
|
||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
|
||||||
#include <kunifiedpush/connector.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class TrayIcon;
|
class TrayIcon;
|
||||||
class QQuickTextDocument;
|
class QQuickTextDocument;
|
||||||
@@ -82,7 +76,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Save an access token to the keychain for the given account.
|
* @brief Save an access token to the keychain for the given account.
|
||||||
*/
|
*/
|
||||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
bool saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
|
||||||
|
|
||||||
[[nodiscard]] bool supportSystemTray() const;
|
[[nodiscard]] bool supportSystemTray() const;
|
||||||
|
|
||||||
@@ -110,7 +104,7 @@ private:
|
|||||||
QPointer<NeoChatConnection> m_connection;
|
QPointer<NeoChatConnection> m_connection;
|
||||||
TrayIcon *m_trayIcon = nullptr;
|
TrayIcon *m_trayIcon = nullptr;
|
||||||
|
|
||||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account);
|
||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ FormCard.FormCardPage {
|
|||||||
implicitWidth: tabBar.tabWidth
|
implicitWidth: tabBar.tabWidth
|
||||||
}
|
}
|
||||||
QQC2.TabButton {
|
QQC2.TabButton {
|
||||||
text: qsTr("Room Data")
|
text: i18nc("@title:tab", "Room Data")
|
||||||
|
|
||||||
implicitWidth: tabBar.tabWidth
|
implicitWidth: tabBar.tabWidth
|
||||||
}
|
}
|
||||||
QQC2.TabButton {
|
QQC2.TabButton {
|
||||||
text: qsTr("Server Info")
|
text: i18nc("@title:tab", "Server Info")
|
||||||
|
|
||||||
implicitWidth: tabBar.tabWidth
|
implicitWidth: tabBar.tabWidth
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,14 @@ FormCard.FormCardPage {
|
|||||||
|
|
||||||
onToggled: Config.secretBackup = checked
|
onToggled: Config.secretBackup = checked
|
||||||
}
|
}
|
||||||
|
FormCard.FormCheckDelegate {
|
||||||
|
text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs")
|
||||||
|
checked: Config.phone3PId
|
||||||
|
|
||||||
|
onToggled: {
|
||||||
|
Config.phone3PId = checked
|
||||||
|
Config.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/enums/powerlevel.cpp
Normal file
109
src/enums/powerlevel.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#include "powerlevel.h"
|
||||||
|
|
||||||
|
QString PowerLevel::nameForLevel(Level level)
|
||||||
|
{
|
||||||
|
switch (level) {
|
||||||
|
case PowerLevel::Member:
|
||||||
|
return i18n("Member");
|
||||||
|
case PowerLevel::Moderator:
|
||||||
|
return i18n("Moderator");
|
||||||
|
case PowerLevel::Admin:
|
||||||
|
return i18n("Admin");
|
||||||
|
case PowerLevel::Mute:
|
||||||
|
return i18n("Mute");
|
||||||
|
case PowerLevel::Custom:
|
||||||
|
return i18n("Custom");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PowerLevel::valueForLevel(Level level)
|
||||||
|
{
|
||||||
|
switch (level) {
|
||||||
|
case PowerLevel::Member:
|
||||||
|
return 0;
|
||||||
|
case PowerLevel::Moderator:
|
||||||
|
return 50;
|
||||||
|
case PowerLevel::Admin:
|
||||||
|
return 100;
|
||||||
|
case PowerLevel::Mute:
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerLevel::Level PowerLevel::levelForValue(int value)
|
||||||
|
{
|
||||||
|
switch (value) {
|
||||||
|
case 0:
|
||||||
|
return PowerLevel::Member;
|
||||||
|
case 50:
|
||||||
|
return PowerLevel::Moderator;
|
||||||
|
case 100:
|
||||||
|
return PowerLevel::Admin;
|
||||||
|
case -1:
|
||||||
|
return PowerLevel::Mute;
|
||||||
|
default:
|
||||||
|
return PowerLevel::Custom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerLevelModel::PowerLevelModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PowerLevelModel::showMute() const
|
||||||
|
{
|
||||||
|
return m_showMute;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PowerLevelModel::setShowMute(bool showMute)
|
||||||
|
{
|
||||||
|
if (showMute == m_showMute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showMute = showMute;
|
||||||
|
Q_EMIT showMuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PowerLevelModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (index.row() >= rowCount()) {
|
||||||
|
qDebug() << "PowerLevelModel, something's wrong: index.row() >= m_rules.count()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto level = static_cast<PowerLevel::Level>(index.row());
|
||||||
|
if (role == NameRole) {
|
||||||
|
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
|
||||||
|
"%1 (%2)",
|
||||||
|
PowerLevel::nameForLevel(level),
|
||||||
|
PowerLevel::valueForLevel(level));
|
||||||
|
}
|
||||||
|
if (role == ValueRole) {
|
||||||
|
return PowerLevel::valueForLevel(level);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int PowerLevelModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return PowerLevel::NUMLevels - (m_showMute ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> PowerLevelModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {{NameRole, "name"}, {ValueRole, "value"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_powerlevel.cpp"
|
||||||
110
src/enums/powerlevel.h
Normal file
110
src/enums/powerlevel.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// 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 once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PowerLevel
|
||||||
|
*
|
||||||
|
* This class is designed to define the PowerLevel enumeration.
|
||||||
|
*/
|
||||||
|
class PowerLevel : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The type of delegate that is needed for the event.
|
||||||
|
*
|
||||||
|
* @note While similar this is not the matrix event or message type. This is
|
||||||
|
* to tell a QML ListView what delegate to show for each event. So while
|
||||||
|
* similar to the spec it is not the same.
|
||||||
|
*/
|
||||||
|
enum Level {
|
||||||
|
Member, /**< A basic member. */
|
||||||
|
Moderator, /**< A moderator with enhanced powers. */
|
||||||
|
Admin, /**< The highest power level in the room. */
|
||||||
|
Mute, /**< The level to remove posting privileges. */
|
||||||
|
NUMLevels,
|
||||||
|
Custom, /**< A non-standard value. Intentionally after NUMLevels so it doesn't appear in the model. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a string representation of the enum value.
|
||||||
|
*/
|
||||||
|
static QString nameForLevel(Level level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the integer representation of the enum value.
|
||||||
|
*/
|
||||||
|
static int valueForLevel(Level level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the enum value for the given integer power level.
|
||||||
|
*/
|
||||||
|
static Level levelForValue(int value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PowerLevelModel
|
||||||
|
*
|
||||||
|
* A model visualize the allowed power levels.
|
||||||
|
*/
|
||||||
|
class PowerLevelModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(bool showMute READ showMute WRITE setShowMute NOTIFY showMuteChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
|
enum Roles {
|
||||||
|
NameRole = Qt::DisplayRole, /**< The power level name. */
|
||||||
|
ValueRole, /**< The power level value. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
|
explicit PowerLevelModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] bool showMute() const;
|
||||||
|
void setShowMute(bool showMute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void showMuteChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_showMute = true;
|
||||||
|
};
|
||||||
@@ -293,7 +293,10 @@ QString EventHandler::getMarkdownBody() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
|
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
|
||||||
return roomMessageEvent->plainBody();
|
|
||||||
|
QString plainBody = roomMessageEvent->plainBody();
|
||||||
|
plainBody.remove(TextRegex::removeReply);
|
||||||
|
return plainBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
|
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ FakeRunner::FakeRunner()
|
|||||||
qDBusRegisterMetaType<RemoteImage>();
|
qDBusRegisterMetaType<RemoteImage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_fakerunner.cpp"
|
#include "moc_fakerunner.cpp"
|
||||||
|
|||||||
156
src/identityserverhelper.cpp
Normal file
156
src/identityserverhelper.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#include "identityserverhelper.h"
|
||||||
|
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include <Quotient/networkaccessmanager.h>
|
||||||
|
|
||||||
|
#include "neochatconnection.h"
|
||||||
|
|
||||||
|
IdentityServerHelper::IdentityServerHelper(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoChatConnection *IdentityServerHelper::connection() const
|
||||||
|
{
|
||||||
|
return m_connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityServerHelper::setConnection(NeoChatConnection *connection)
|
||||||
|
{
|
||||||
|
if (m_connection == connection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_connection != nullptr) {
|
||||||
|
m_connection->disconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connection = connection;
|
||||||
|
Q_EMIT connectionChanged();
|
||||||
|
Q_EMIT currentServerChanged();
|
||||||
|
|
||||||
|
connect(m_connection, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||||
|
if (type == QLatin1String("m.identity_server")) {
|
||||||
|
Q_EMIT currentServerChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QString IdentityServerHelper::currentServer() const
|
||||||
|
{
|
||||||
|
if (m_connection == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
|
||||||
|
return i18nc("@info", "No identity server configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
|
||||||
|
if (!url.isEmpty()) {
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
return i18nc("@info", "No identity server configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IdentityServerHelper::hasCurrentServer() const
|
||||||
|
{
|
||||||
|
if (m_connection == nullptr && !m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
|
||||||
|
if (!url.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString IdentityServerHelper::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityServerHelper::setUrl(const QString &url)
|
||||||
|
{
|
||||||
|
if (url == m_url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_url = url;
|
||||||
|
Q_EMIT urlChanged();
|
||||||
|
|
||||||
|
checkUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityServerHelper::IdServerStatus IdentityServerHelper::status() const
|
||||||
|
{
|
||||||
|
return m_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityServerHelper::checkUrl()
|
||||||
|
{
|
||||||
|
if (m_idServerCheckRequest != nullptr) {
|
||||||
|
m_idServerCheckRequest->abort();
|
||||||
|
m_idServerCheckRequest.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_url == currentServer()) {
|
||||||
|
m_status = Match;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_url.isEmpty()) {
|
||||||
|
m_status = Valid;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto requestUrl = QUrl(m_url + QStringLiteral("/_matrix/identity/v2"));
|
||||||
|
if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) {
|
||||||
|
m_status = Invalid;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkRequest request(requestUrl);
|
||||||
|
m_idServerCheckRequest = Quotient::NetworkAccessManager::instance()->get(request);
|
||||||
|
connect(m_idServerCheckRequest, &QNetworkReply::finished, this, [this]() {
|
||||||
|
if (m_idServerCheckRequest->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||||
|
m_status = Valid;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
} else {
|
||||||
|
m_status = Invalid;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityServerHelper::setIdentityServer()
|
||||||
|
{
|
||||||
|
if (m_url == currentServer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), m_url}});
|
||||||
|
m_status = Ready;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentityServerHelper::clearIdentityServer()
|
||||||
|
{
|
||||||
|
if (currentServer().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), QString()}});
|
||||||
|
m_status = Ready;
|
||||||
|
Q_EMIT statusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_identityserverhelper.cpp"
|
||||||
103
src/identityserverhelper.h
Normal file
103
src/identityserverhelper.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// 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 once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
|
||||||
|
class NeoChatConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class IdentityServerHelper
|
||||||
|
*
|
||||||
|
* This class is designed to help the process of setting an identity server for the account.
|
||||||
|
* It will manage the various stages of verification and authentication.
|
||||||
|
*/
|
||||||
|
class IdentityServerHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The connection to add a 3PID to.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current identity server.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString currentServer READ currentServer NOTIFY currentServerChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether an identity server is currently configured.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool hasCurrentServer READ hasCurrentServer NOTIFY currentServerChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The URL for the desired server.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current status.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(IdServerStatus status READ status NOTIFY statusChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The current status for adding an identity server
|
||||||
|
*/
|
||||||
|
enum IdServerStatus {
|
||||||
|
Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new server. */
|
||||||
|
Valid, /**< The server URL is valid. */
|
||||||
|
Invalid, /**< The server URL is invalid. */
|
||||||
|
Match, /**< The server URL is the one that is already configured. */
|
||||||
|
Other, /**< An unknown problem occurred. */
|
||||||
|
};
|
||||||
|
Q_ENUM(IdServerStatus)
|
||||||
|
|
||||||
|
explicit IdentityServerHelper(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] NeoChatConnection *connection() const;
|
||||||
|
void setConnection(NeoChatConnection *connection);
|
||||||
|
|
||||||
|
[[nodiscard]] QString currentServer() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool hasCurrentServer() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QString url() const;
|
||||||
|
void setUrl(const QString &url);
|
||||||
|
|
||||||
|
[[nodiscard]] IdServerStatus status() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the current URL as the user's identity server.
|
||||||
|
*
|
||||||
|
* Will do nothing if the URL isn't a valid identity server.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void setIdentityServer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the user's identity server.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void clearIdentityServer();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void connectionChanged();
|
||||||
|
void currentServerChanged();
|
||||||
|
void urlChanged();
|
||||||
|
void statusChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<NeoChatConnection> m_connection;
|
||||||
|
|
||||||
|
IdServerStatus m_status = Ready;
|
||||||
|
QString m_url;
|
||||||
|
|
||||||
|
QPointer<QNetworkReply> m_idServerCheckRequest;
|
||||||
|
|
||||||
|
void checkUrl();
|
||||||
|
};
|
||||||
16
src/jobs/neochatadd3pidjob.cpp
Normal file
16
src/jobs/neochatadd3pidjob.cpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#include "neochatadd3pidjob.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable<QJsonObject> &auth)
|
||||||
|
: BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), makePath("/_matrix/client/v3", "/account/3pid/add"))
|
||||||
|
{
|
||||||
|
QJsonObject _dataJson;
|
||||||
|
addParam<IfNotEmpty>(_dataJson, QStringLiteral("auth"), auth);
|
||||||
|
addParam<>(_dataJson, QStringLiteral("client_secret"), clientSecret);
|
||||||
|
addParam<>(_dataJson, QStringLiteral("sid"), sid);
|
||||||
|
setRequestData({_dataJson});
|
||||||
|
}
|
||||||
13
src/jobs/neochatadd3pidjob.h
Normal file
13
src/jobs/neochatadd3pidjob.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// 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 once
|
||||||
|
|
||||||
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
|
class NeochatAdd3PIdJob : public Quotient::BaseJob
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
|
||||||
|
};
|
||||||
@@ -89,38 +89,23 @@ bool LinkPreviewer::empty() const
|
|||||||
return m_url.isEmpty();
|
return m_url.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
|
QList<QUrl> LinkPreviewer::linkPreviews(QString string)
|
||||||
{
|
{
|
||||||
if (event == nullptr) {
|
auto data = string.remove(TextRegex::removeRichReply);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text;
|
|
||||||
if (event->hasTextContent()) {
|
|
||||||
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
|
|
||||||
if (textContent) {
|
|
||||||
text = textContent->body;
|
|
||||||
} else {
|
|
||||||
text = event->plainBody();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = event->plainBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto data = text.remove(TextRegex::removeRichReply);
|
|
||||||
auto linksMatch = TextRegex::url.globalMatch(data);
|
auto linksMatch = TextRegex::url.globalMatch(data);
|
||||||
|
QList<QUrl> links;
|
||||||
while (linksMatch.hasNext()) {
|
while (linksMatch.hasNext()) {
|
||||||
auto link = linksMatch.next().captured();
|
auto link = linksMatch.next().captured();
|
||||||
if (!link.contains(QStringLiteral("matrix.to"))) {
|
if (!link.contains(QStringLiteral("matrix.to")) && !links.contains(QUrl(link))) {
|
||||||
return QUrl(link);
|
links += QUrl(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
|
bool LinkPreviewer::hasPreviewableLinks(const QString &string)
|
||||||
{
|
{
|
||||||
return !linkPreview(event).isEmpty();
|
return !linkPreviews(string).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_linkpreviewer.cpp"
|
#include "moc_linkpreviewer.cpp"
|
||||||
|
|||||||
@@ -7,11 +7,6 @@
|
|||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
namespace Quotient
|
|
||||||
{
|
|
||||||
class RoomMessageEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,19 +66,19 @@ public:
|
|||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the given event has at least 1 pre-viewable link.
|
* @brief Whether the given string has at least 1 pre-viewable link.
|
||||||
*
|
*
|
||||||
* A link is only pre-viewable if it is http, https or something starting with www.
|
* A link is only pre-viewable if it is http, https or something starting with www.
|
||||||
*/
|
*/
|
||||||
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
|
static bool hasPreviewableLinks(const QString &string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return the link to be previewed from the given event.
|
* @brief Return previewable links from the given string.
|
||||||
*
|
*
|
||||||
* This function is designed to give only links that should be previewed so
|
* This function is designed to give only links that should be previewed so
|
||||||
* http, https or something starting with www. The first valid link is returned.
|
* http, https or something starting with www. The first valid link is returned.
|
||||||
*/
|
*/
|
||||||
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
|
static QList<QUrl> linkPreviews(QString string);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_loaded;
|
bool m_loaded;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <Quotient/accountregistry.h>
|
#include <Quotient/accountregistry.h>
|
||||||
#include <Quotient/qt_connection_util.h>
|
#include <Quotient/qt_connection_util.h>
|
||||||
|
#include <Quotient/settings.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ void LoginHelper::init()
|
|||||||
account.setHomeserver(m_connection->homeserver());
|
account.setHomeserver(m_connection->homeserver());
|
||||||
account.setDeviceId(m_connection->deviceId());
|
account.setDeviceId(m_connection->deviceId());
|
||||||
account.setDeviceName(m_deviceName);
|
account.setDeviceName(m_deviceName);
|
||||||
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
|
if (!Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken())) {
|
||||||
qWarning() << "Couldn't save access token";
|
qWarning() << "Couldn't save access token";
|
||||||
}
|
}
|
||||||
account.sync();
|
account.sync();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QNetworkDiskCache>
|
#include <QNetworkDiskCache>
|
||||||
#include <QNetworkProxyFactory>
|
#include <QNetworkProxyFactory>
|
||||||
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
@@ -46,11 +47,10 @@
|
|||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "matriximageprovider.h"
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "windowcontroller.h"
|
|
||||||
#include "sharehandler.h"
|
#include "sharehandler.h"
|
||||||
|
#include "windowcontroller.h"
|
||||||
|
|
||||||
#ifdef HAVE_RUNNER
|
#ifdef HAVE_RUNNER
|
||||||
#include "runner.h"
|
#include "runner.h"
|
||||||
@@ -236,6 +236,7 @@ int main(int argc, char *argv[])
|
|||||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
|
||||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
|
||||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
|
||||||
|
|
||||||
qml_register_types_org_kde_neochat();
|
qml_register_types_org_kde_neochat();
|
||||||
|
|
||||||
@@ -285,7 +286,6 @@ int main(int argc, char *argv[])
|
|||||||
ShareHandler::instance().setText(parser.value(shareOption));
|
ShareHandler::instance().setText(parser.value(shareOption));
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
|
|
||||||
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
||||||
|
|
||||||
engine.loadFromModule("org.kde.neochat", "Main");
|
engine.loadFromModule("org.kde.neochat", "Main");
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ QVariant AccountEmoticonModel::data(const QModelIndex &index, int role) const
|
|||||||
const auto &row = index.row();
|
const auto &row = index.row();
|
||||||
const auto &image = m_images->images[row];
|
const auto &image = m_images->images[row];
|
||||||
if (role == UrlRole) {
|
if (role == UrlRole) {
|
||||||
return m_connection->makeMediaUrl(image.url);
|
return m_connection->makeMediaUrl(image.url).toString();
|
||||||
}
|
}
|
||||||
if (role == BodyRole) {
|
if (role == BodyRole) {
|
||||||
if (image.body) {
|
if (image.body) {
|
||||||
@@ -163,7 +163,11 @@ void AccountEmoticonModel::setEmoticonImage(int index, const QUrl &source)
|
|||||||
QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl source)
|
QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl source)
|
||||||
{
|
{
|
||||||
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
||||||
|
#if Quotient_VERSION_MINOR > 8
|
||||||
|
co_await qCoro(job.get(), &BaseJob::finished);
|
||||||
|
#else
|
||||||
co_await qCoro(job, &BaseJob::finished);
|
co_await qCoro(job, &BaseJob::finished);
|
||||||
|
#endif
|
||||||
if (job->error() != BaseJob::NoError) {
|
if (job->error() != BaseJob::NoError) {
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
@@ -185,7 +189,11 @@ QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl sourc
|
|||||||
QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString shortcode, QString description, QString type)
|
QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString shortcode, QString description, QString type)
|
||||||
{
|
{
|
||||||
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
||||||
|
#if Quotient_VERSION_MINOR > 8
|
||||||
|
co_await qCoro(job.get(), &BaseJob::finished);
|
||||||
|
#else
|
||||||
co_await qCoro(job, &BaseJob::finished);
|
co_await qCoro(job, &BaseJob::finished);
|
||||||
|
#endif
|
||||||
if (job->error() != BaseJob::NoError) {
|
if (job->error() != BaseJob::NoError) {
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,13 +153,13 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
|
|||||||
|
|
||||||
switch (Roles(role)) {
|
switch (Roles(role)) {
|
||||||
case Roles::ModelData:
|
case Roles::ModelData:
|
||||||
return QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + data.url.mid(6), data.name, true));
|
return QVariant::fromValue(Emoji(m_connection->makeMediaUrl(QUrl(data.url)).toString(), data.name, true));
|
||||||
case Roles::Name:
|
case Roles::Name:
|
||||||
case Roles::DisplayRole:
|
case Roles::DisplayRole:
|
||||||
case Roles::ReplacedTextRole:
|
case Roles::ReplacedTextRole:
|
||||||
return data.name;
|
return data.name;
|
||||||
case Roles::ImageURL:
|
case Roles::ImageURL:
|
||||||
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
return m_connection->makeMediaUrl(QUrl(data.url));
|
||||||
case Roles::MxcUrl:
|
case Roles::MxcUrl:
|
||||||
return m_connection->makeMediaUrl(QUrl(data.url));
|
return m_connection->makeMediaUrl(QUrl(data.url));
|
||||||
default:
|
default:
|
||||||
@@ -205,7 +205,7 @@ QVariantList CustomEmojiModel::filterModel(const QString &filter)
|
|||||||
if (!emoji.name.contains(filter, Qt::CaseInsensitive))
|
if (!emoji.name.contains(filter, Qt::CaseInsensitive))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
results << QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + emoji.url.mid(6), emoji.name, true));
|
results << QVariant::fromValue(Emoji(m_connection->makeMediaUrl(QUrl(emoji.url)).toString(), emoji.name, true));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,8 +128,12 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
|
|||||||
authData["type"_ls] = "m.login.password"_ls;
|
authData["type"_ls] = "m.login.password"_ls;
|
||||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
|
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
|
||||||
authData["identifier"_ls] = identifier;
|
authData["identifier"_ls] = identifier;
|
||||||
auto *innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
auto innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||||
|
#if Quotient_VERSION_MINOR > 8
|
||||||
|
connect(innerJob.get(), &BaseJob::success, this, onSuccess);
|
||||||
|
#else
|
||||||
connect(innerJob, &BaseJob::success, this, onSuccess);
|
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,11 +110,14 @@ MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoCh
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, &MessageContentModel::updateLinkPreviewer);
|
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &MessageContentModel::updateLinkPreviewer);
|
updateComponents();
|
||||||
|
});
|
||||||
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||||
|
updateComponents();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLinkPreviewer();
|
|
||||||
updateComponents();
|
updateComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +200,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
return eventHandler.getReplyMediaInfo();
|
return eventHandler.getReplyMediaInfo();
|
||||||
}
|
}
|
||||||
if (role == LinkPreviewerRole) {
|
if (role == LinkPreviewerRole) {
|
||||||
if (m_linkPreviewer != nullptr) {
|
if (component.type == MessageComponentType::LinkPreview) {
|
||||||
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewer);
|
return QVariant::fromValue<LinkPreviewer *>(
|
||||||
|
dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(component.attributes["link"_ls].toUrl()));
|
||||||
} else {
|
} else {
|
||||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||||
}
|
}
|
||||||
@@ -272,12 +276,8 @@ void MessageContentModel::updateComponents(bool isEditing)
|
|||||||
m_components.append(componentsForType(eventHandler.messageComponentType()));
|
m_components.append(componentsForType(eventHandler.messageComponentType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_linkPreviewer != nullptr) {
|
if (m_room->urlPreviewEnabled()) {
|
||||||
if (m_linkPreviewer->loaded()) {
|
addLinkPreviews();
|
||||||
m_components += MessageComponent{MessageComponentType::LinkPreview, QString(), {}};
|
|
||||||
} else {
|
|
||||||
m_components += MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {}};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
@@ -294,6 +294,9 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
case MessageComponentType::File: {
|
case MessageComponentType::File: {
|
||||||
QList<MessageComponent> components;
|
QList<MessageComponent> components;
|
||||||
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||||
|
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||||
|
auto body = EventHandler::rawMessageBody(*event);
|
||||||
|
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||||
if (m_emptyItinerary) {
|
if (m_emptyItinerary) {
|
||||||
auto fileTransferInfo = fileInfo();
|
auto fileTransferInfo = fileInfo();
|
||||||
|
|
||||||
@@ -321,46 +324,77 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
}
|
}
|
||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
|
case MessageComponentType::Image:
|
||||||
|
case MessageComponentType::Video: {
|
||||||
|
if (!m_event->is<StickerEvent>()) {
|
||||||
|
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||||
|
QList<MessageComponent> components;
|
||||||
|
components += MessageComponent{type, QString(), {}};
|
||||||
|
auto body = EventHandler::rawMessageBody(*event);
|
||||||
|
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return {MessageComponent{type, QString(), {}}};
|
return {MessageComponent{type, QString(), {}}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::updateLinkPreviewer()
|
MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||||
{
|
{
|
||||||
if (m_room == nullptr || m_event == nullptr) {
|
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||||
if (m_linkPreviewer != nullptr) {
|
if (linkPreviewer == nullptr) {
|
||||||
m_linkPreviewer->disconnect(this);
|
return {};
|
||||||
m_linkPreviewer = nullptr;
|
|
||||||
updateComponents();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!m_room->urlPreviewEnabled()) {
|
if (linkPreviewer->loaded()) {
|
||||||
if (m_linkPreviewer != nullptr) {
|
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
|
||||||
m_linkPreviewer->disconnect(this);
|
} else {
|
||||||
m_linkPreviewer = nullptr;
|
connect(linkPreviewer, &LinkPreviewer::loadedChanged, [this, link]() {
|
||||||
updateComponents();
|
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||||
}
|
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||||
return;
|
for (auto &component : m_components) {
|
||||||
}
|
if (component.attributes["link"_ls].toUrl() == link) {
|
||||||
|
|
||||||
if (const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
|
||||||
if (LinkPreviewer::hasPreviewableLinks(event)) {
|
|
||||||
m_linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(LinkPreviewer::linkPreview(event));
|
|
||||||
updateComponents();
|
|
||||||
|
|
||||||
if (m_linkPreviewer != nullptr) {
|
|
||||||
connect(m_linkPreviewer, &LinkPreviewer::loadedChanged, [this]() {
|
|
||||||
if (m_linkPreviewer != nullptr && m_linkPreviewer->loaded()) {
|
|
||||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_components[m_components.size() - 1].type = MessageComponentType::LinkPreview;
|
component.type = MessageComponentType::LinkPreview;
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::addLinkPreviews()
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (i < m_components.size()) {
|
||||||
|
const auto component = m_components.at(i);
|
||||||
|
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
|
||||||
|
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
|
||||||
|
const auto links = LinkPreviewer::linkPreviews(component.content);
|
||||||
|
for (qsizetype j = 0; j < links.size(); ++j) {
|
||||||
|
const auto linkPreview = linkPreviewComponent(links[j]);
|
||||||
|
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
|
||||||
|
m_components.insert(i + j + 1, linkPreview);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::closeLinkPreview(int row)
|
||||||
|
{
|
||||||
|
if (m_components[row].type == MessageComponentType::LinkPreview || m_components[row].type == MessageComponentType::LinkPreviewLoad) {
|
||||||
|
beginResetModel();
|
||||||
|
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
||||||
|
m_components.remove(row);
|
||||||
|
m_components.squeeze();
|
||||||
|
updateComponents();
|
||||||
|
endResetModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,9 @@
|
|||||||
#include "enums/messagecomponenttype.h"
|
#include "enums/messagecomponenttype.h"
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "itinerarymodel.h"
|
#include "itinerarymodel.h"
|
||||||
#include "linkpreviewer.h"
|
|
||||||
#include "neochatroom.h"
|
|
||||||
|
|
||||||
struct MessageComponent {
|
struct MessageComponent {
|
||||||
MessageComponentType::Type type;
|
MessageComponentType::Type type = MessageComponentType::Other;
|
||||||
QString content;
|
QString content;
|
||||||
QVariantMap attributes;
|
QVariantMap attributes;
|
||||||
|
|
||||||
@@ -23,6 +21,11 @@ struct MessageComponent {
|
|||||||
{
|
{
|
||||||
return type == right.type && content == right.content && attributes == right.attributes;
|
return type == right.type && content == right.content && attributes == right.attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isEmpty() const
|
||||||
|
{
|
||||||
|
return type == MessageComponentType::Other;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +91,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Close the link preview at the given index.
|
||||||
|
*
|
||||||
|
* If the given index is not a link preview component, nothing happens.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void closeLinkPreview(int row);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatRoom> m_room;
|
QPointer<NeoChatRoom> m_room;
|
||||||
const Quotient::RoomEvent *m_event = nullptr;
|
const Quotient::RoomEvent *m_event = nullptr;
|
||||||
@@ -95,12 +105,14 @@ private:
|
|||||||
QList<MessageComponent> m_components;
|
QList<MessageComponent> m_components;
|
||||||
void updateComponents(bool isEditing = false);
|
void updateComponents(bool isEditing = false);
|
||||||
|
|
||||||
QPointer<LinkPreviewer> m_linkPreviewer;
|
|
||||||
ItineraryModel *m_itineraryModel = nullptr;
|
ItineraryModel *m_itineraryModel = nullptr;
|
||||||
|
|
||||||
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
||||||
|
MessageComponent linkPreviewComponent(const QUrl &link);
|
||||||
|
void addLinkPreviews();
|
||||||
|
|
||||||
|
QList<QUrl> m_removedLinkPreviews;
|
||||||
|
|
||||||
void updateLinkPreviewer();
|
|
||||||
void updateItineraryModel();
|
void updateItineraryModel();
|
||||||
bool m_emptyItinerary = false;
|
bool m_emptyItinerary = false;
|
||||||
|
|
||||||
|
|||||||
254
src/models/permissionsmodel.cpp
Normal file
254
src/models/permissionsmodel.cpp
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#include "permissionsmodel.h"
|
||||||
|
#include "powerlevel.h"
|
||||||
|
|
||||||
|
#include <Quotient/events/roompowerlevelsevent.h>
|
||||||
|
|
||||||
|
#include <KLazyLocalizedString>
|
||||||
|
|
||||||
|
static const QStringList defaultPermissions = {
|
||||||
|
QStringLiteral("users_default"),
|
||||||
|
QStringLiteral("state_default"),
|
||||||
|
QStringLiteral("events_default"),
|
||||||
|
QStringLiteral("invite"),
|
||||||
|
QStringLiteral("kick"),
|
||||||
|
QStringLiteral("ban"),
|
||||||
|
QStringLiteral("redact"),
|
||||||
|
QStringLiteral("m.reaction"),
|
||||||
|
QStringLiteral("m.room.redaction"),
|
||||||
|
QStringLiteral("m.room.power_levels"),
|
||||||
|
QStringLiteral("m.room.name"),
|
||||||
|
QStringLiteral("m.room.avatar"),
|
||||||
|
QStringLiteral("m.room.canonical_alias"),
|
||||||
|
QStringLiteral("m.room.topic"),
|
||||||
|
QStringLiteral("m.room.encryption"),
|
||||||
|
QStringLiteral("m.room.history_visibility"),
|
||||||
|
QStringLiteral("m.room.pinned_events"),
|
||||||
|
QStringLiteral("m.room.tombstone"),
|
||||||
|
QStringLiteral("m.room.server_acl"),
|
||||||
|
QStringLiteral("m.space.child"),
|
||||||
|
QStringLiteral("m.space.parent"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Alternate name text for default permissions.
|
||||||
|
static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||||
|
{QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")},
|
||||||
|
{QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")},
|
||||||
|
{QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")},
|
||||||
|
{QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")},
|
||||||
|
{QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")},
|
||||||
|
{QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")},
|
||||||
|
{QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||||
|
{QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")},
|
||||||
|
{QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")},
|
||||||
|
{QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")},
|
||||||
|
{QStringLiteral("m.room.name"), kli18nc("Room permission type", "Change the room name")},
|
||||||
|
{QStringLiteral("m.room.avatar"), kli18nc("Room permission type", "Change the room avatar")},
|
||||||
|
{QStringLiteral("m.room.canonical_alias"), kli18nc("Room permission type", "Change the room canonical alias")},
|
||||||
|
{QStringLiteral("m.room.topic"), kli18nc("Room permission type", "Change the room topic")},
|
||||||
|
{QStringLiteral("m.room.encryption"), kli18nc("Room permission type", "Enable encryption for the room")},
|
||||||
|
{QStringLiteral("m.room.history_visibility"), kli18nc("Room permission type", "Change the room history visibility")},
|
||||||
|
{QStringLiteral("m.room.pinned_events"), kli18nc("Room permission type", "Set pinned events")},
|
||||||
|
{QStringLiteral("m.room.tombstone"), kli18nc("Room permission type", "Upgrade the room")},
|
||||||
|
{QStringLiteral("m.room.server_acl"), kli18nc("Room permission type", "Set the room server access control list (ACL)")},
|
||||||
|
{QStringLiteral("m.space.child"), kli18nc("Room permission type", "Set the children of this space")},
|
||||||
|
{QStringLiteral("m.space.parent"), kli18nc("Room permission type", "Set the parent space of this room")},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Subtitles for the default values.
|
||||||
|
static const QHash<QString, KLazyLocalizedString> defaultSubtitles = {
|
||||||
|
{QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||||
|
{QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||||
|
{QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Permissions that should use the event default.
|
||||||
|
static const QStringList eventPermissions = {
|
||||||
|
QStringLiteral("m.room.message"),
|
||||||
|
QStringLiteral("m.reaction"),
|
||||||
|
QStringLiteral("m.room.redaction"),
|
||||||
|
};
|
||||||
|
|
||||||
|
PermissionsModel::PermissionsModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoChatRoom *PermissionsModel::room() const
|
||||||
|
{
|
||||||
|
return m_room;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PermissionsModel::setRoom(NeoChatRoom *room)
|
||||||
|
{
|
||||||
|
if (room == m_room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_room = room;
|
||||||
|
Q_EMIT roomChanged();
|
||||||
|
|
||||||
|
initializeModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PermissionsModel::initializeModel()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
|
||||||
|
m_permissions.clear();
|
||||||
|
|
||||||
|
if (m_room == nullptr) {
|
||||||
|
endResetModel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>();
|
||||||
|
if (currentPowerLevelEvent == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_permissions.append(defaultPermissions);
|
||||||
|
|
||||||
|
for (const auto &event : currentPowerLevelEvent->events().keys()) {
|
||||||
|
if (!m_permissions.contains(event)) {
|
||||||
|
m_permissions += event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (m_room == nullptr || !index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (index.row() >= rowCount()) {
|
||||||
|
qDebug() << "PushRuleModel, something's wrong: index.row() >= m_rules.count()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto permission = m_permissions.value(index.row());
|
||||||
|
if (role == NameRole) {
|
||||||
|
if (defaultPermissionNames.keys().contains(permission)) {
|
||||||
|
return defaultPermissionNames.value(permission).toString();
|
||||||
|
}
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
if (role == SubtitleRole) {
|
||||||
|
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
if (defaultSubtitles.contains(permission)) {
|
||||||
|
return defaultSubtitles.value(permission).toString();
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
if (role == TypeRole) {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
if (role == LevelRole) {
|
||||||
|
const auto level = powerLevel(permission);
|
||||||
|
if (level.has_value()) {
|
||||||
|
return *level;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (role == LevelNameRole) {
|
||||||
|
const auto level = powerLevel(permission);
|
||||||
|
if (level.has_value()) {
|
||||||
|
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
|
||||||
|
"%1 (%2)",
|
||||||
|
PowerLevel::nameForLevel(PowerLevel::levelForValue(*level)),
|
||||||
|
*level);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
if (role == IsDefaultValueRole) {
|
||||||
|
return permission.contains(QLatin1String("default"));
|
||||||
|
}
|
||||||
|
if (role == IsBasicPermissionRole) {
|
||||||
|
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|
||||||
|
|| permission == QStringLiteral("redact");
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int PermissionsModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return m_permissions.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> PermissionsModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||||
|
roles[NameRole] = "name";
|
||||||
|
roles[SubtitleRole] = "subtitle";
|
||||||
|
roles[TypeRole] = "type";
|
||||||
|
roles[LevelRole] = "level";
|
||||||
|
roles[LevelNameRole] = "levelName";
|
||||||
|
roles[IsDefaultValueRole] = "isDefaultValue";
|
||||||
|
roles[IsBasicPermissionRole] = "isBasicPermission";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
|
||||||
|
{
|
||||||
|
if (m_room == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||||
|
if (permission == QStringLiteral("ban")) {
|
||||||
|
return currentPowerLevelEvent->ban();
|
||||||
|
} else if (permission == QStringLiteral("kick")) {
|
||||||
|
return currentPowerLevelEvent->kick();
|
||||||
|
} else if (permission == QStringLiteral("invite")) {
|
||||||
|
return currentPowerLevelEvent->invite();
|
||||||
|
} else if (permission == QStringLiteral("redact")) {
|
||||||
|
return currentPowerLevelEvent->redact();
|
||||||
|
} else if (permission == QStringLiteral("users_default")) {
|
||||||
|
return currentPowerLevelEvent->usersDefault();
|
||||||
|
} else if (permission == QStringLiteral("state_default")) {
|
||||||
|
return currentPowerLevelEvent->stateDefault();
|
||||||
|
} else if (permission == QStringLiteral("events_default")) {
|
||||||
|
return currentPowerLevelEvent->eventsDefault();
|
||||||
|
} else if (eventPermissions.contains(permission)) {
|
||||||
|
return currentPowerLevelEvent->powerLevelForEvent(permission);
|
||||||
|
} else {
|
||||||
|
return currentPowerLevelEvent->powerLevelForState(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PermissionsModel::setPowerLevel(const QString &permission, const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
if (m_room == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int clampPowerLevel = std::clamp(newPowerLevel, -1, 100);
|
||||||
|
|
||||||
|
const auto currentPowerLevel = powerLevel(permission);
|
||||||
|
if (!currentPowerLevel.has_value() || currentPowerLevel == clampPowerLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||||
|
auto powerLevelContent = currentPowerLevelEvent->contentJson();
|
||||||
|
if (powerLevelContent.contains(permission)) {
|
||||||
|
powerLevelContent[permission] = clampPowerLevel;
|
||||||
|
} else {
|
||||||
|
auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject();
|
||||||
|
eventPowerLevels[permission] = clampPowerLevel;
|
||||||
|
powerLevelContent[QLatin1String("events")] = eventPowerLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_room->setState<Quotient::RoomPowerLevelsEvent>(powerLevelContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_permissionsmodel.cpp"
|
||||||
87
src/models/permissionsmodel.h
Normal file
87
src/models/permissionsmodel.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// 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 once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PermissionsModel
|
||||||
|
*
|
||||||
|
* This class defines the model for managing room permission levels.
|
||||||
|
*/
|
||||||
|
class PermissionsModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The room to show the permissions for
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
|
enum Roles {
|
||||||
|
NameRole = Qt::DisplayRole, /**< The permission name. */
|
||||||
|
SubtitleRole, /**< The description of the permission. */
|
||||||
|
TypeRole, /**< The base type of the permission, normally the event type id except for ban, kick, etc. */
|
||||||
|
LevelRole, /**< The current power level for the permission. */
|
||||||
|
LevelNameRole, /**< The current power level for the permission as a string. */
|
||||||
|
IsDefaultValueRole, /**< Whether the permission is a default value, e.g. for users. */
|
||||||
|
IsBasicPermissionRole, /**< Whether the permission is one of the basic ones, e.g. kick, ban, etc. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
|
explicit PermissionsModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
NeoChatRoom *room() const;
|
||||||
|
void setRoom(NeoChatRoom *room);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the power level required for the given permission.
|
||||||
|
*/
|
||||||
|
std::optional<int> powerLevel(const QString &permission) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the power level required for the given permission.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void setPowerLevel(const QString &permission, const int &newPowerLevel);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void roomChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<NeoChatRoom> m_room;
|
||||||
|
|
||||||
|
QStringList m_permissions;
|
||||||
|
|
||||||
|
void initializeModel();
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFont>
|
||||||
#ifdef HAVE_ICU
|
#ifdef HAVE_ICU
|
||||||
#include <QTextBoundaryFinder>
|
#include <QTextBoundaryFinder>
|
||||||
#include <QTextCharFormat>
|
#include <QTextCharFormat>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
|
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconnection.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ private:
|
|||||||
|
|
||||||
QString m_searchText;
|
QString m_searchText;
|
||||||
QPointer<NeoChatRoom> m_room;
|
QPointer<NeoChatRoom> m_room;
|
||||||
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
std::optional<Quotient::SearchJob::ResultRoomEvents> m_result = std::nullopt;
|
||||||
Quotient::SearchJob *m_job = nullptr;
|
Quotient::SearchJob *m_job = nullptr;
|
||||||
bool m_searching = false;
|
bool m_searching = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,15 +10,7 @@ ThreePIdModel::ThreePIdModel(NeoChatConnection *connection)
|
|||||||
{
|
{
|
||||||
Q_ASSERT(connection);
|
Q_ASSERT(connection);
|
||||||
connect(connection, &NeoChatConnection::stateChanged, this, [this]() {
|
connect(connection, &NeoChatConnection::stateChanged, this, [this]() {
|
||||||
const auto connection = dynamic_cast<NeoChatConnection *>(this->parent());
|
refreshModel();
|
||||||
if (connection != nullptr && connection->isLoggedIn()) {
|
|
||||||
const auto threePIdJob = connection->callApi<Quotient::GetAccount3PIDsJob>();
|
|
||||||
connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() {
|
|
||||||
beginResetModel();
|
|
||||||
m_threePIds = threePIdJob->threepids();
|
|
||||||
endResetModel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,4 +48,17 @@ QHash<int, QByteArray> ThreePIdModel::roleNames() const
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThreePIdModel::refreshModel()
|
||||||
|
{
|
||||||
|
const auto connection = dynamic_cast<NeoChatConnection *>(this->parent());
|
||||||
|
if (connection != nullptr && connection->isLoggedIn()) {
|
||||||
|
const auto threePIdJob = connection->callApi<Quotient::GetAccount3PIDsJob>();
|
||||||
|
connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() {
|
||||||
|
beginResetModel();
|
||||||
|
m_threePIds = threePIdJob->threepids();
|
||||||
|
endResetModel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_threepidmodel.cpp"
|
#include "moc_threepidmodel.cpp"
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void refreshModel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVector<Quotient::GetAccount3PIDsJob::ThirdPartyIdentifier> m_threePIds;
|
QVector<Quotient::GetAccount3PIDsJob::ThirdPartyIdentifier> m_threePIds;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <Quotient/events/roompowerlevelsevent.h>
|
#include <Quotient/events/roompowerlevelsevent.h>
|
||||||
|
|
||||||
|
#include "enums/powerlevel.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -94,16 +95,10 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
auto userPl = pl->powerLevelForUser(user->id());
|
auto userPl = pl->powerLevelForUser(user->id());
|
||||||
|
|
||||||
switch (userPl) {
|
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
|
||||||
case 0:
|
"%1 (%2)",
|
||||||
return QStringLiteral("Member");
|
PowerLevel::nameForLevel(PowerLevel::levelForValue(userPl)),
|
||||||
case 50:
|
userPl);
|
||||||
return QStringLiteral("Moderator");
|
|
||||||
case 100:
|
|
||||||
return QStringLiteral("Admin");
|
|
||||||
default:
|
|
||||||
return QStringLiteral("Custom");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -202,7 +202,6 @@ Name[pl]=Nowe zaproszenie
|
|||||||
Name[pt]=Novo Convite
|
Name[pt]=Novo Convite
|
||||||
Name[pt_BR]=Novo convite
|
Name[pt_BR]=Novo convite
|
||||||
Name[ru]=Новое приглашение
|
Name[ru]=Новое приглашение
|
||||||
Name[sk]=Nová pozvánka
|
|
||||||
Name[sl]=Novo povabilo
|
Name[sl]=Novo povabilo
|
||||||
Name[sv]=Ny inbjudan
|
Name[sv]=Ny inbjudan
|
||||||
Name[ta]=புதிய அழைப்பிதழ்
|
Name[ta]=புதிய அழைப்பிதழ்
|
||||||
@@ -242,7 +241,6 @@ Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
|||||||
Comment[pt]=Existe um novo convite para uma sala
|
Comment[pt]=Existe um novo convite para uma sala
|
||||||
Comment[pt_BR]=Existe um novo convite para uma sala
|
Comment[pt_BR]=Existe um novo convite para uma sala
|
||||||
Comment[ru]=Доступно новое приглашение в комнату
|
Comment[ru]=Доступно новое приглашение в комнату
|
||||||
Comment[sk]=Máte novú pozvánku do miestnosti
|
|
||||||
Comment[sl]=Tam je novo povabilo v sobo
|
Comment[sl]=Tam je novo povabilo v sobo
|
||||||
Comment[sv]=Det finns en ny inbjudan till ett rum
|
Comment[sv]=Det finns en ny inbjudan till ett rum
|
||||||
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
||||||
@@ -255,15 +253,13 @@ Action=Popup
|
|||||||
|
|
||||||
[Event/Share]
|
[Event/Share]
|
||||||
Name=Share
|
Name=Share
|
||||||
Name[ar]=شارك
|
|
||||||
Name[ca]=Compartició
|
Name[ca]=Compartició
|
||||||
Name[ca@valencia]=Compartiu
|
Name[ca@valencia]=Compartició
|
||||||
Name[cs]=Sdílet
|
Name[cs]=Sdílet
|
||||||
Name[en_GB]=Share
|
Name[en_GB]=Share
|
||||||
Name[eo]=Kundividi
|
Name[eo]=Kundividi
|
||||||
Name[es]=Compartir
|
Name[es]=Compartir
|
||||||
Name[eu]=Partekatu
|
Name[eu]=Partekatu
|
||||||
Name[fi]=Jaa
|
|
||||||
Name[fr]=Partager
|
Name[fr]=Partager
|
||||||
Name[hu]=Megosztás
|
Name[hu]=Megosztás
|
||||||
Name[ia]=Comparti
|
Name[ia]=Comparti
|
||||||
@@ -271,25 +267,20 @@ Name[it]=Condivisione
|
|||||||
Name[ka]=გაზიარება
|
Name[ka]=გაზიარება
|
||||||
Name[lv]=Kopīgot
|
Name[lv]=Kopīgot
|
||||||
Name[nl]=Gedeelde
|
Name[nl]=Gedeelde
|
||||||
Name[nn]=Del
|
|
||||||
Name[pl]=Udostępnij
|
Name[pl]=Udostępnij
|
||||||
Name[sk]=Zdieľať
|
|
||||||
Name[sl]=Deli
|
Name[sl]=Deli
|
||||||
Name[sv]=Dela
|
|
||||||
Name[ta]=பகிர்
|
Name[ta]=பகிர்
|
||||||
Name[tr]=Paylaş
|
Name[tr]=Paylaş
|
||||||
Name[uk]=Оприлюднення
|
Name[uk]=Оприлюднення
|
||||||
Name[x-test]=xxSharexx
|
Name[x-test]=xxSharexx
|
||||||
Name[zh_TW]=分享
|
Name[zh_TW]=分享
|
||||||
Comment=The result of sharing a piece of content
|
Comment=The result of sharing a piece of content
|
||||||
Comment[ar]=نتيجة مشاركة محتوى
|
|
||||||
Comment[ca]=El resultat de compartir una peça de contingut
|
Comment[ca]=El resultat de compartir una peça de contingut
|
||||||
Comment[ca@valencia]=El resultat de compartir una peça de contingut
|
Comment[ca@valencia]=El resultat de compartir una peça de contingut
|
||||||
Comment[en_GB]=The result of sharing a piece of content
|
Comment[en_GB]=The result of sharing a piece of content
|
||||||
Comment[eo]=La rezulto el kundividado de enhavero
|
Comment[eo]=La rezulto el kundividado de enhavero
|
||||||
Comment[es]=El resultado de compartir una parte de contenido
|
Comment[es]=El resultado de compartir una parte de contenido
|
||||||
Comment[eu]=Eduki pieza bat partekatzearen emaitza
|
Comment[eu]=Eduki pieza bat partekatzearen emaitza
|
||||||
Comment[fi]=Tulos yhden sisältöosasen jakamisesta
|
|
||||||
Comment[fr]=Le résultat du partage d'une partie de contenu.
|
Comment[fr]=Le résultat du partage d'une partie de contenu.
|
||||||
Comment[hu]=Tartalom megosztásának eredménye
|
Comment[hu]=Tartalom megosztásának eredménye
|
||||||
Comment[ia]=Le exito de compartir un pecietta de contento
|
Comment[ia]=Le exito de compartir un pecietta de contento
|
||||||
@@ -297,11 +288,8 @@ Comment[it]=Il risultato della condivisione di un contenuto
|
|||||||
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
||||||
Comment[lv]=Satura kopīgošanas rezultāts
|
Comment[lv]=Satura kopīgošanas rezultāts
|
||||||
Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||||
Comment[nn]=Resultatet av deling av innhald
|
|
||||||
Comment[pl]=Wynik udostępniania kawałka treści
|
Comment[pl]=Wynik udostępniania kawałka treści
|
||||||
Comment[sk]=Výsledok zdieľania obsahu
|
|
||||||
Comment[sl]=Rezultat deljenega kosa vsebine
|
Comment[sl]=Rezultat deljenega kosa vsebine
|
||||||
Comment[sv]=Resultatet av att dela innehåll
|
|
||||||
Comment[ta]=எதையோ பகிர்ந்ததன் விளைவு
|
Comment[ta]=எதையோ பகிர்ந்ததன் விளைவு
|
||||||
Comment[tr]=Bir parça içerik paylaşımının sonucu
|
Comment[tr]=Bir parça içerik paylaşımının sonucu
|
||||||
Comment[uk]=Результат оприлюднення даних
|
Comment[uk]=Результат оприлюднення даних
|
||||||
|
|||||||
@@ -136,6 +136,9 @@
|
|||||||
<choice name="Socks5">
|
<choice name="Socks5">
|
||||||
<label>Socks5</label>
|
<label>Socks5</label>
|
||||||
</choice>
|
</choice>
|
||||||
|
<choice name="NoProxy">
|
||||||
|
<label>NoProxy</label>
|
||||||
|
</choice>
|
||||||
<default>System</default>
|
<default>System</default>
|
||||||
</choices>
|
</choices>
|
||||||
</entry>
|
</entry>
|
||||||
@@ -179,6 +182,10 @@
|
|||||||
<label>Enable secret backup</label>
|
<label>Enable secret backup</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry name="Phone3PId" type="bool">
|
||||||
|
<label>Enable add phone numbers as 3PIDs</label>
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
</group>
|
</group>
|
||||||
</kcfg>
|
</kcfg>
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
setHasFileUploading(false);
|
setHasFileUploading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(this, &Room::aboutToAddHistoricalMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
|
connect(this, &Room::addedMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
|
||||||
|
|
||||||
const auto &roomLastMessageProvider = RoomLastMessageProvider::self();
|
const auto &roomLastMessageProvider = RoomLastMessageProvider::self();
|
||||||
|
|
||||||
@@ -876,7 +876,7 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
|
|||||||
qWarning() << "User is not a member of this room so power level cannot be set";
|
qWarning() << "User is not a member of this room so power level cannot be set";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
|
int clampPowerLevel = std::clamp(powerLevel, -1, 100);
|
||||||
|
|
||||||
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
|
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
|
||||||
auto powerLevelUserOverrides = powerLevelContent["users"_ls].toObject();
|
auto powerLevelUserOverrides = powerLevelContent["users"_ls].toObject();
|
||||||
@@ -898,254 +898,6 @@ int NeoChatRoom::getUserPowerLevel(const QString &userId) const
|
|||||||
return powerLevelEvent->powerLevelForUser(userId);
|
return powerLevelEvent->powerLevelForUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
|
|
||||||
{
|
|
||||||
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
|
|
||||||
if (eventName == "ban"_ls) {
|
|
||||||
return powerLevelEvent->ban();
|
|
||||||
} else if (eventName == "kick"_ls) {
|
|
||||||
return powerLevelEvent->kick();
|
|
||||||
} else if (eventName == "invite"_ls) {
|
|
||||||
return powerLevelEvent->invite();
|
|
||||||
} else if (eventName == "redact"_ls) {
|
|
||||||
return powerLevelEvent->redact();
|
|
||||||
} else if (eventName == "users_default"_ls) {
|
|
||||||
return powerLevelEvent->usersDefault();
|
|
||||||
} else if (eventName == "state_default"_ls) {
|
|
||||||
return powerLevelEvent->stateDefault();
|
|
||||||
} else if (eventName == "events_default"_ls) {
|
|
||||||
return powerLevelEvent->eventsDefault();
|
|
||||||
} else if (isStateEvent) {
|
|
||||||
return powerLevelEvent->powerLevelForState(eventName);
|
|
||||||
} else {
|
|
||||||
return powerLevelEvent->powerLevelForEvent(eventName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
|
|
||||||
{
|
|
||||||
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
|
|
||||||
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
|
|
||||||
int powerLevel = 0;
|
|
||||||
|
|
||||||
if (powerLevelContent.contains(eventName)) {
|
|
||||||
powerLevel = powerLevelContent[eventName].toInt();
|
|
||||||
|
|
||||||
if (powerLevel != clampPowerLevel) {
|
|
||||||
powerLevelContent[eventName] = clampPowerLevel;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto eventPowerLevels = powerLevelContent["events"_ls].toObject();
|
|
||||||
|
|
||||||
if (eventPowerLevels.contains(eventName)) {
|
|
||||||
powerLevel = eventPowerLevels[eventName].toInt();
|
|
||||||
} else {
|
|
||||||
if (isStateEvent) {
|
|
||||||
powerLevel = powerLevelContent["state_default"_ls].toInt();
|
|
||||||
} else {
|
|
||||||
powerLevel = powerLevelContent["events_default"_ls].toInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powerLevel != clampPowerLevel) {
|
|
||||||
eventPowerLevels[eventName] = clampPowerLevel;
|
|
||||||
powerLevelContent["events"_ls] = eventPowerLevels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState("m.room.power_levels"_ls, {}, powerLevelContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::defaultUserPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("users_default"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setDefaultUserPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("users_default"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::invitePowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("invite"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setInvitePowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("invite"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::kickPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("kick"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setKickPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("kick"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::banPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("ban"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setBanPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("ban"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::redactPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("redact"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setRedactPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("redact"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::statePowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("state_default"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setStatePowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("state_default"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::defaultEventPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("events_default"_ls);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setDefaultEventPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("events_default"_ls, newPowerLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::powerLevelPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.power_levels"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setPowerLevelPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.power_levels"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::namePowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.name"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setNamePowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.name"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::avatarPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.avatar"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setAvatarPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.avatar"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::canonicalAliasPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.canonical_alias"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setCanonicalAliasPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.canonical_alias"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::topicPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.topic"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setTopicPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.topic"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::encryptionPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.encryption"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setEncryptionPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.encryption"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::historyVisibilityPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.history_visibility"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setHistoryVisibilityPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.history_visibility"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::pinnedEventsPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.pinned_events"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setPinnedEventsPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.pinned_events"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::tombstonePowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.tombstone"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setTombstonePowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.tombstone"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::serverAclPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.room.server_acl"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setServerAclPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.room.server_acl"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::spaceChildPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.space.child"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setSpaceChildPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.space.child"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::spaceParentPowerLevel() const
|
|
||||||
{
|
|
||||||
return powerLevel("m.space.parent"_ls, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::setSpaceParentPowerLevel(const int &newPowerLevel)
|
|
||||||
{
|
|
||||||
setPowerLevel("m.space.parent"_ls, newPowerLevel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
||||||
{
|
{
|
||||||
QStringList events;
|
QStringList events;
|
||||||
@@ -1156,7 +908,11 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
|
|||||||
}
|
}
|
||||||
for (const auto &e : events) {
|
for (const auto &e : events) {
|
||||||
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
|
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
|
||||||
|
#if Quotient_VERSION_MINOR > 8
|
||||||
|
co_await qCoro(job.get(), &BaseJob::finished);
|
||||||
|
#else
|
||||||
co_await qCoro(job, &BaseJob::finished);
|
co_await qCoro(job, &BaseJob::finished);
|
||||||
|
#endif
|
||||||
if (job->error() != BaseJob::Success) {
|
if (job->error() != BaseJob::Success) {
|
||||||
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
||||||
break;
|
break;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user