Compare commits
136 Commits
release/25
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508c2bee46 | ||
|
|
e1bbbfe4fd | ||
|
|
7a2211f8e0 | ||
|
|
4155e9116a | ||
|
|
a989ef42b2 | ||
|
|
2babf44b28 | ||
|
|
89e42dbc53 | ||
|
|
ba57570dbf | ||
|
|
1a500a087b | ||
|
|
e54955ec0c | ||
|
|
8608b3b62e | ||
|
|
fea0cfbf4e | ||
|
|
5b6e5a25e5 | ||
|
|
58b47b8711 | ||
|
|
b45967508c | ||
|
|
2ec1fa92fa | ||
|
|
7602a56594 | ||
|
|
e8da02be7d | ||
|
|
71c84be4b4 | ||
|
|
b684fb451d | ||
|
|
3a416990ca | ||
|
|
9b7d16973d | ||
|
|
dd59e63574 | ||
|
|
7f7e48dfd4 | ||
|
|
7119132e4b | ||
|
|
6001cc6d4f | ||
|
|
989f1ad020 | ||
|
|
a119563ba7 | ||
|
|
5e473aae0a | ||
|
|
aa0272a02d | ||
|
|
fc6f345036 | ||
|
|
4fa011c266 | ||
|
|
44ebcc6b9a | ||
|
|
32cc5d4e48 | ||
|
|
bbc5a92f62 | ||
|
|
4656bf4ee7 | ||
|
|
5f7967363f | ||
|
|
a02a04d966 | ||
|
|
dffec2f0d5 | ||
|
|
68b00b9fc5 | ||
|
|
aa823c7629 | ||
|
|
73b82b69d8 | ||
|
|
bd0588ca99 | ||
|
|
e5b7601dac | ||
|
|
f4ac3346f5 | ||
|
|
58ea229b67 | ||
|
|
fd44ff972a | ||
|
|
c5eb63da15 | ||
|
|
79acceb29a | ||
|
|
8022a7aae5 | ||
|
|
d26862dfb6 | ||
|
|
c25c095c1b | ||
|
|
e09e4fb7dc | ||
|
|
657c8a0dcd | ||
|
|
82f54b4f2c | ||
|
|
9e7cd0eb09 | ||
|
|
9bdb9b6a7c | ||
|
|
fa4a93140d | ||
|
|
a41966ecd7 | ||
|
|
edcf81f0da | ||
|
|
0a98c732e0 | ||
|
|
d734e114a6 | ||
|
|
946e505a24 | ||
|
|
092980c0ff | ||
|
|
6fcb1cc1e3 | ||
|
|
c572480d10 | ||
|
|
531df7a3b2 | ||
|
|
706f1f7836 | ||
|
|
45c5806c5a | ||
|
|
abf37c90cb | ||
|
|
5d9508b165 | ||
|
|
f5a652f7a1 | ||
|
|
f165cb1f10 | ||
|
|
11132c3e89 | ||
|
|
089ab2009a | ||
|
|
6ebedc5dfc | ||
|
|
e5b2fb7316 | ||
|
|
5da3aff607 | ||
|
|
3fa46d52fe | ||
|
|
ece9811d82 | ||
|
|
b543b1c2f9 | ||
|
|
5065bccb64 | ||
|
|
10c3125668 | ||
|
|
73019cfea2 | ||
|
|
4bc6a88acf | ||
|
|
56a49cfc50 | ||
|
|
c50380b448 | ||
|
|
673a8c8bc8 | ||
|
|
c298c348a4 | ||
|
|
2fcd535aeb | ||
|
|
8f5e68de5d | ||
|
|
5fe36a9057 | ||
|
|
8d8c6444e0 | ||
|
|
03d5955c8d | ||
|
|
197d7ce8e8 | ||
|
|
efbf6d96d4 | ||
|
|
b8cd3c69c2 | ||
|
|
1da24191f0 | ||
|
|
2ecc567792 | ||
|
|
66e1fe067d | ||
|
|
d08f014def | ||
|
|
63941d6685 | ||
|
|
38523c97c5 | ||
|
|
65c6f4c1d3 | ||
|
|
0cb3fd32f4 | ||
|
|
b02cb0157e | ||
|
|
ca163fc169 | ||
|
|
5687eb33e1 | ||
|
|
fc795e50b3 | ||
|
|
c4adfe7939 | ||
|
|
ce96232bbd | ||
|
|
95653cf3f7 | ||
|
|
0c11cd331f | ||
|
|
3c8ca0d421 | ||
|
|
52f71d5c55 | ||
|
|
00f7dd5175 | ||
|
|
52da8415e9 | ||
|
|
953e4cd792 | ||
|
|
ee4b1578b1 | ||
|
|
ae24424a32 | ||
|
|
51be282824 | ||
|
|
c539dfc352 | ||
|
|
fe734206df | ||
|
|
79a49165f1 | ||
|
|
ada5eef088 | ||
|
|
9cc5778126 | ||
|
|
b129805f94 | ||
|
|
887865c0aa | ||
|
|
1336194cf4 | ||
|
|
5a7ae3563e | ||
|
|
7c56e24fdc | ||
|
|
e6b9abeca3 | ||
|
|
1b45784a88 | ||
|
|
a12b0d5282 | ||
|
|
a6d75f2ff5 | ||
|
|
b8f7f33b9a |
@@ -2,7 +2,7 @@
|
|||||||
"id": "org.kde.neochat",
|
"id": "org.kde.neochat",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"runtime": "org.kde.Platform",
|
"runtime": "org.kde.Platform",
|
||||||
"runtime-version": "6.9",
|
"runtime-version": "6.10",
|
||||||
"sdk": "org.kde.Sdk",
|
"sdk": "org.kde.Sdk",
|
||||||
"command": "neochat",
|
"command": "neochat",
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -31,19 +31,6 @@
|
|||||||
"/share/ndk-modules"
|
"/share/ndk-modules"
|
||||||
],
|
],
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
|
||||||
"name": "kirigamiaddons",
|
|
||||||
"config-opts": [
|
|
||||||
"-DBUILD_TESTING=OFF"
|
|
||||||
],
|
|
||||||
"buildsystem": "cmake-ninja",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "opencv",
|
"name": "opencv",
|
||||||
"config-opts": [
|
"config-opts": [
|
||||||
@@ -78,6 +65,7 @@
|
|||||||
"name": "olm",
|
"name": "olm",
|
||||||
"buildsystem": "cmake-ninja",
|
"buildsystem": "cmake-ninja",
|
||||||
"config-opts": [
|
"config-opts": [
|
||||||
|
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||||
"-DOLM_TESTS=OFF"
|
"-DOLM_TESTS=OFF"
|
||||||
],
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
@@ -184,8 +172,8 @@
|
|||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.kde.org/stable/release-service/25.08.0/src/kunifiedpush-25.08.0.tar.xz",
|
"url": "https://download.kde.org/stable/release-service/25.08.3/src/kunifiedpush-25.08.3.tar.xz",
|
||||||
"sha256": "846db6ffc7d93f6afea7ce0d5a9f10b52792157ceb593856542279f4197f3518",
|
"sha256": "e8c924438d5359f0fa0930ab35111012076e3a0ff4e959d6929595571383320a",
|
||||||
"x-checker-data": {
|
"x-checker-data": {
|
||||||
"type": "anitya",
|
"type": "anitya",
|
||||||
"project-id": 8763,
|
"project-id": 8763,
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ Dependencies:
|
|||||||
'frameworks/ki18n': '@latest-kf6'
|
'frameworks/ki18n': '@latest-kf6'
|
||||||
'frameworks/kconfig': '@latest-kf6'
|
'frameworks/kconfig': '@latest-kf6'
|
||||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||||
|
'frameworks/kiconthemes': '@latest-kf6'
|
||||||
'frameworks/kitemmodels': '@latest-kf6'
|
'frameworks/kitemmodels': '@latest-kf6'
|
||||||
'frameworks/kquickcharts': '@latest-kf6'
|
'frameworks/kquickcharts': '@latest-kf6'
|
||||||
'frameworks/knotifications': '@latest-kf6'
|
'frameworks/knotifications': '@latest-kf6'
|
||||||
'frameworks/kcolorscheme': '@latest-kf6'
|
'frameworks/kcolorscheme': '@latest-kf6'
|
||||||
'frameworks/kiconthemes': '@latest-kf6'
|
|
||||||
'libraries/kquickimageeditor': '@latest-kf6'
|
'libraries/kquickimageeditor': '@latest-kf6'
|
||||||
'frameworks/sonnet': '@latest-kf6'
|
'frameworks/sonnet': '@latest-kf6'
|
||||||
'frameworks/prison': '@latest-kf6'
|
'frameworks/prison': '@latest-kf6'
|
||||||
@@ -29,7 +29,6 @@ Dependencies:
|
|||||||
'frameworks/kio': '@latest-kf6'
|
'frameworks/kio': '@latest-kf6'
|
||||||
'frameworks/kwindowsystem': '@latest-kf6'
|
'frameworks/kwindowsystem': '@latest-kf6'
|
||||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||||
'frameworks/kcrash': '@latest-kf6'
|
|
||||||
- 'on': ['Linux', 'FreeBSD']
|
- 'on': ['Linux', 'FreeBSD']
|
||||||
'require':
|
'require':
|
||||||
'frameworks/kdbusaddons': '@latest-kf6'
|
'frameworks/kdbusaddons': '@latest-kf6'
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
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 "25")
|
set(RELEASE_SERVICE_VERSION_MAJOR "26")
|
||||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
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}")
|
||||||
|
|
||||||
@@ -46,10 +46,6 @@ if (NOT ANDROID)
|
|||||||
include(KDEClangFormat)
|
include(KDEClangFormat)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NEOCHAT_FLATPAK)
|
|
||||||
include(cmake/Flatpak.cmake)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
|
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
|
||||||
|
|
||||||
ecm_set_disabled_deprecation_versions(Qt 6.9.0 KF 6.17.0)
|
ecm_set_disabled_deprecation_versions(Qt 6.9.0 KF 6.17.0)
|
||||||
@@ -69,7 +65,7 @@ if (QT_KNOWN_POLICY_QTP0004)
|
|||||||
qt_policy(SET QTP0004 NEW)
|
qt_policy(SET QTP0004 NEW)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme)
|
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme IconThemes)
|
||||||
set_package_properties(KF6 PROPERTIES
|
set_package_properties(KF6 PROPERTIES
|
||||||
TYPE REQUIRED
|
TYPE REQUIRED
|
||||||
PURPOSE "Basic application components"
|
PURPOSE "Basic application components"
|
||||||
@@ -92,7 +88,7 @@ if(ANDROID)
|
|||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem Crash)
|
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
|
||||||
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
|
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
|
||||||
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
|
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
|
||||||
TYPE RUNTIME
|
TYPE RUNTIME
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ void ActionsTest::testActions_data()
|
|||||||
QTest::addColumn<std::optional<QString>>("resultText");
|
QTest::addColumn<std::optional<QString>>("resultText");
|
||||||
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
|
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
|
||||||
|
|
||||||
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\_(ツ)_/¯ Hello"_s)
|
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
|
||||||
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||||
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||||
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
|
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
|
|||||||
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
||||||
model->setRoom(room);
|
model->setRoom(room);
|
||||||
|
|
||||||
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
|
QCOMPARE(model->indexForEventId(u"$153456789:example.org"_s).row(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineMessageModelTest::cleanup()
|
void TimelineMessageModelTest::cleanup()
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
|
|
||||||
# Include FontConfig config which uses the Emoji One font from the
|
|
||||||
# KDE Flatpak SDK.
|
|
||||||
install(
|
|
||||||
FILES
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
|
|
||||||
DESTINATION
|
|
||||||
${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
|
||||||
<fontconfig>
|
|
||||||
<alias>
|
|
||||||
<family>serif</family>
|
|
||||||
<prefer>
|
|
||||||
<family>Noto Color Emoji</family>
|
|
||||||
</prefer>
|
|
||||||
</alias>
|
|
||||||
<alias>
|
|
||||||
<family>sans-serif</family>
|
|
||||||
<prefer>
|
|
||||||
<family>Noto Color Emoji</family>
|
|
||||||
</prefer>
|
|
||||||
</alias>
|
|
||||||
<alias>
|
|
||||||
<family>monospace</family>
|
|
||||||
<prefer>
|
|
||||||
<family>Noto Color Emoji</family>
|
|
||||||
</prefer>
|
|
||||||
</alias>
|
|
||||||
</fontconfig>
|
|
||||||
|
|
||||||
@@ -320,7 +320,6 @@
|
|||||||
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
|
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
|
||||||
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
|
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
|
||||||
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
||||||
<value key="KDE::supporters">Anonymous donor, Akseli</value>
|
|
||||||
</custom>
|
</custom>
|
||||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||||
<screenshots>
|
<screenshots>
|
||||||
@@ -488,6 +487,8 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="25.12.1" date="2026-01-08"/>
|
||||||
|
<release version="25.12.0" date="2025-12-11"/>
|
||||||
<release version="25.08.3" date="2025-11-06"/>
|
<release version="25.08.3" date="2025-11-06"/>
|
||||||
<release version="25.08.2" date="2025-10-09"/>
|
<release version="25.08.2" date="2025-10-09"/>
|
||||||
<release version="25.08.1" date="2025-09-11"/>
|
<release version="25.08.1" date="2025-09-11"/>
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ Comment[ia]=Conversation en ditecto sur Matrix
|
|||||||
Comment[it]= su Matrix
|
Comment[it]= su Matrix
|
||||||
Comment[ka]=ჩატი Matrix-ზე
|
Comment[ka]=ჩატი Matrix-ზე
|
||||||
Comment[ko]=Matrix에서 대화하기
|
Comment[ko]=Matrix에서 대화하기
|
||||||
|
Comment[lt]=Pokalbiai per Matrix
|
||||||
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
||||||
Comment[nl]=Chat op Matrix
|
Comment[nl]=Chat op Matrix
|
||||||
Comment[pl]=Rozmawiaj na Matriksie
|
Comment[pl]=Rozmawiaj na Matriksie
|
||||||
|
|||||||
1253
po/ar/neochat.po
1253
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1289
po/az/neochat.po
1289
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1203
po/ca/neochat.po
1203
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1177
po/cs/neochat.po
1177
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1199
po/da/neochat.po
1199
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
2290
po/de/neochat.po
2290
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1345
po/el/neochat.po
1345
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1376
po/en_GB/neochat.po
1376
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1364
po/eo/neochat.po
1364
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1111
po/es/neochat.po
1111
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1525
po/eu/neochat.po
1525
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1564
po/fi/neochat.po
1564
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1301
po/fr/neochat.po
1301
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
7146
po/ga/neochat.po
Normal file
7146
po/ga/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1377
po/gl/neochat.po
1377
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1231
po/he/neochat.po
1231
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
1356
po/hi/neochat.po
1356
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
1721
po/hu/neochat.po
1721
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1365
po/ia/neochat.po
1365
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1331
po/id/neochat.po
1331
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1272
po/ie/neochat.po
1272
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1282
po/it/neochat.po
1282
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
963
po/ja/neochat.po
963
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1230
po/ka/neochat.po
1230
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1357
po/ko/neochat.po
1357
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1303
po/lt/neochat.po
1303
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1595
po/lv/neochat.po
1595
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1244
po/nl/neochat.po
1244
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1213
po/nn/neochat.po
1213
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1279
po/pa/neochat.po
1279
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1280
po/pl/neochat.po
1280
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1325
po/pt/neochat.po
1325
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
122
po/pt_BR/docs/neochat/man-neochat.1.docbook
Normal file
122
po/pt_BR/docs/neochat/man-neochat.1.docbook
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||||
|
<!ENTITY % Brazilian-Portuguese "INCLUDE">
|
||||||
|
]>
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
<refentry lang="&language;">
|
||||||
|
<refentryinfo>
|
||||||
|
<title
|
||||||
|
>Manual do Usuário do NeoChat</title>
|
||||||
|
<author
|
||||||
|
><firstname
|
||||||
|
>Carl</firstname
|
||||||
|
><surname
|
||||||
|
>Schwan</surname
|
||||||
|
> <contrib
|
||||||
|
>NeoChat man page.</contrib
|
||||||
|
> <email
|
||||||
|
>carl@carlschwan.eu</email
|
||||||
|
></author>
|
||||||
|
<date
|
||||||
|
>01/11/2022</date>
|
||||||
|
<releaseinfo
|
||||||
|
>22.09</releaseinfo>
|
||||||
|
<productname
|
||||||
|
>NeoChat</productname>
|
||||||
|
</refentryinfo>
|
||||||
|
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>
|
||||||
|
<command
|
||||||
|
>neochat</command>
|
||||||
|
</refentrytitle>
|
||||||
|
<manvolnum
|
||||||
|
>1</manvolnum>
|
||||||
|
</refmeta>
|
||||||
|
|
||||||
|
<refnamediv>
|
||||||
|
<refname
|
||||||
|
>neochat</refname>
|
||||||
|
<refpurpose
|
||||||
|
>Cliente para interação com o protocolo de mensagens Matrix.</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
<!-- body begins here -->
|
||||||
|
<refsynopsisdiv id='synopsis'>
|
||||||
|
<cmdsynopsis
|
||||||
|
><command
|
||||||
|
>neochat</command
|
||||||
|
> <arg choice="opt"
|
||||||
|
><replaceable
|
||||||
|
>URI</replaceable
|
||||||
|
></arg
|
||||||
|
> </cmdsynopsis>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
|
||||||
|
<refsect1 id="description">
|
||||||
|
<title
|
||||||
|
>Descrição</title>
|
||||||
|
<para
|
||||||
|
>O <command
|
||||||
|
>neochat</command
|
||||||
|
> é um aplicativo de bate-papo para o protocolo Matrix. Ele funciona tanto em computadores quanto em dispositivos móveis. </para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="options"
|
||||||
|
><title
|
||||||
|
>Opções</title>
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term
|
||||||
|
><option
|
||||||
|
>URI</option
|
||||||
|
></term>
|
||||||
|
<listitem>
|
||||||
|
<para
|
||||||
|
>O URI da matriz para um usuário ou uma sala. Por exemplo, matrix:u/usuário:exemplo.org e matrix:r/root:exemplo.org. Isso fará com que o NeoChat tente abrir a sala ou conversa especificada. </para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="bug">
|
||||||
|
<title
|
||||||
|
>Relatar bugs</title>
|
||||||
|
<para
|
||||||
|
>Você pode reportar erros e solicitar novas funcionalidades em <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General"
|
||||||
|
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General</ulink
|
||||||
|
></para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title
|
||||||
|
>Veja também</title>
|
||||||
|
<simplelist>
|
||||||
|
<member
|
||||||
|
>Lista de perguntas frequentes sobre o Matrix <ulink url="https://matrix.org/faq/"
|
||||||
|
>https://matrix.org/faq/</ulink
|
||||||
|
> </member>
|
||||||
|
<member
|
||||||
|
>kf5options(7)</member>
|
||||||
|
<member
|
||||||
|
>qt5options(7)</member>
|
||||||
|
</simplelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="copyright"
|
||||||
|
><title
|
||||||
|
>Direitos autorais</title>
|
||||||
|
<para
|
||||||
|
>Direitos autorais © 2020-2022 Tobias Fella </para>
|
||||||
|
<para
|
||||||
|
>Direitos autorais © 2020-2022 Carl Schwan </para>
|
||||||
|
<para
|
||||||
|
>Licença: GNU General Public Versão 3 ou posterior <ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||||
|
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||||
|
>></para>
|
||||||
|
</refsect1>
|
||||||
|
</refentry>
|
||||||
1256
po/pt_BR/neochat.po
1256
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1193
po/ro/neochat.po
1193
po/ro/neochat.po
File diff suppressed because it is too large
Load Diff
1399
po/ru/neochat.po
1399
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1362
po/sa/neochat.po
1362
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
1299
po/sk/neochat.po
1299
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1191
po/sl/neochat.po
1191
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1365
po/sv/neochat.po
1365
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1506
po/ta/neochat.po
1506
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1192
po/tok/neochat.po
1192
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||||||
>carl@carlschwan.eu</email
|
>carl@carlschwan.eu</email
|
||||||
></author>
|
></author>
|
||||||
<date
|
<date
|
||||||
>2022-11-01</date>
|
>2022‒11‒01</date>
|
||||||
<releaseinfo
|
<releaseinfo
|
||||||
>22.09</releaseinfo>
|
>22.09</releaseinfo>
|
||||||
<productname
|
<productname
|
||||||
@@ -111,9 +111,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||||||
><title
|
><title
|
||||||
>Telif Hakkı</title>
|
>Telif Hakkı</title>
|
||||||
<para
|
<para
|
||||||
>Telif hakkı © 2020-2022 Tobias Fella </para>
|
>Telif hakkı © 2020–2022 Tobias Fella </para>
|
||||||
<para
|
<para
|
||||||
>Telif hakkı © 2020-2022 Carl Schwan </para>
|
>Telif hakkı © 2020–2022 Carl Schwan </para>
|
||||||
<para
|
<para
|
||||||
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||||
|
|||||||
1215
po/tr/neochat.po
1215
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1219
po/uk/neochat.po
1219
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1370
po/zh_CN/neochat.po
1370
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1475
po/zh_TW/neochat.po
1475
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
add_library(neochat STATIC
|
qt_add_library(neochat STATIC
|
||||||
controller.cpp
|
controller.cpp
|
||||||
controller.h
|
controller.h
|
||||||
roommanager.cpp
|
roommanager.cpp
|
||||||
@@ -35,6 +35,8 @@ add_library(neochat STATIC
|
|||||||
models/commonroomsmodel.h
|
models/commonroomsmodel.h
|
||||||
texttospeechhelper.h
|
texttospeechhelper.h
|
||||||
texttospeechhelper.cpp
|
texttospeechhelper.cpp
|
||||||
|
models/limitermodel.cpp
|
||||||
|
models/limitermodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||||
@@ -104,6 +106,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
|||||||
qml/NewPollDialog.qml
|
qml/NewPollDialog.qml
|
||||||
qml/UserMenu.qml
|
qml/UserMenu.qml
|
||||||
qml/MeetingDialog.qml
|
qml/MeetingDialog.qml
|
||||||
|
qml/SeenByDialog.qml
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
QtCore
|
QtCore
|
||||||
QtQuick
|
QtQuick
|
||||||
@@ -140,10 +143,17 @@ if(WIN32)
|
|||||||
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
|
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(neochat-app
|
qt_add_executable(neochat-app
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
set_target_properties(neochat-app PROPERTIES
|
||||||
|
OUTPUT_NAME "neochat-app"
|
||||||
|
PREFIX "lib"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(TARGET Qt::WebView)
|
if(TARGET Qt::WebView)
|
||||||
target_link_libraries(neochat-app PUBLIC Qt::WebView)
|
target_link_libraries(neochat-app PUBLIC Qt::WebView)
|
||||||
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
|
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
|
||||||
@@ -153,6 +163,7 @@ target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
|
|||||||
|
|
||||||
target_link_libraries(neochat-app PRIVATE
|
target_link_libraries(neochat-app PRIVATE
|
||||||
neochat
|
neochat
|
||||||
|
KF6::IconThemes
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||||
@@ -183,7 +194,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
||||||
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
target_link_libraries(neochat PRIVATE neochatplugin Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
||||||
target_link_libraries(neochat PUBLIC
|
target_link_libraries(neochat PUBLIC
|
||||||
LibNeoChat
|
LibNeoChat
|
||||||
Timeline
|
Timeline
|
||||||
@@ -203,7 +214,6 @@ target_link_libraries(neochat PUBLIC
|
|||||||
KF6::ConfigGui
|
KF6::ConfigGui
|
||||||
KF6::CoreAddons
|
KF6::CoreAddons
|
||||||
KF6::SonnetCore
|
KF6::SonnetCore
|
||||||
KF6::IconThemes
|
|
||||||
KF6::ItemModels
|
KF6::ItemModels
|
||||||
KF6::I18nQml
|
KF6::I18nQml
|
||||||
KirigamiApp
|
KirigamiApp
|
||||||
@@ -214,10 +224,6 @@ target_link_libraries(neochat PUBLIC
|
|||||||
Spaces
|
Spaces
|
||||||
)
|
)
|
||||||
|
|
||||||
if (TARGET KF6::Crash)
|
|
||||||
target_link_libraries(neochat PUBLIC KF6::Crash)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
|
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
|
||||||
|
|
||||||
if(NEOCHAT_FLATPAK)
|
if(NEOCHAT_FLATPAK)
|
||||||
@@ -357,7 +363,8 @@ endif()
|
|||||||
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
|
||||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
# krunner plugin must be the same as the app id for flatpak to export it
|
||||||
|
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins RENAME org.kde.neochat.desktop)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
|||||||
@@ -246,7 +246,10 @@ void Controller::initActiveConnection(NeoChatConnection *oldConnection, NeoChatC
|
|||||||
if (newConnection) {
|
if (newConnection) {
|
||||||
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
|
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
|
||||||
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
|
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
|
||||||
|
|
||||||
|
// Refresh and update manually, in case we init too late for the badge count to actually change.
|
||||||
newConnection->refreshBadgeNotificationCount();
|
newConnection->refreshBadgeNotificationCount();
|
||||||
|
updateBadgeNotificationCount(newConnection->badgeNotificationCount());
|
||||||
}
|
}
|
||||||
Q_EMIT activeConnectionChanged(newConnection);
|
Q_EMIT activeConnectionChanged(newConnection);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ int main(int argc, char *argv[])
|
|||||||
{
|
{
|
||||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||||
|
|
||||||
|
// We currently need to do this ourselves,
|
||||||
|
// KirigamiApp currently called this after constructing the app which breaks icons on Windows.
|
||||||
|
KIconTheme::initTheme();
|
||||||
|
|
||||||
#ifdef HAVE_WEBVIEW
|
#ifdef HAVE_WEBVIEW
|
||||||
QtWebView::initialize();
|
QtWebView::initialize();
|
||||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||||
@@ -162,12 +166,6 @@ int main(int argc, char *argv[])
|
|||||||
Connection::setEncryptionDefault(true);
|
Connection::setEncryptionDefault(true);
|
||||||
Connection::setDirectChatEncryptionDefault(true);
|
Connection::setDirectChatEncryptionDefault(true);
|
||||||
|
|
||||||
#ifdef NEOCHAT_FLATPAK
|
|
||||||
// Copy over the included FontConfig configuration to the
|
|
||||||
// app's config dir:
|
|
||||||
QFile::copy(u"/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"_s, u"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"_s);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ColorSchemer colorScheme;
|
ColorSchemer colorScheme;
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "jobs/neochatgetcommonroomsjob.h"
|
#include "jobs/neochatgetcommonroomsjob.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
#include <Quotient/room.h>
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -39,8 +40,22 @@ void CommonRoomsModel::setUserId(const QString &userId)
|
|||||||
|
|
||||||
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
|
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(index)
|
auto roomId = m_commonRooms[index.row()];
|
||||||
Q_UNUSED(roleName)
|
auto room = connection()->room(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (roleName) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case RoomNameRole:
|
||||||
|
return room->displayName();
|
||||||
|
case RoomAvatarRole:
|
||||||
|
return room->avatarUrl();
|
||||||
|
case RoomIdRole:
|
||||||
|
return roomId;
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +65,15 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
|
|||||||
return m_commonRooms.size();
|
return m_commonRooms.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{RoomIdRole, "roomId"},
|
||||||
|
{RoomNameRole, "roomName"},
|
||||||
|
{RoomAvatarRole, "roomAvatar"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void CommonRoomsModel::reload()
|
void CommonRoomsModel::reload()
|
||||||
{
|
{
|
||||||
if (!m_connection || m_userId.isEmpty()) {
|
if (!m_connection || m_userId.isEmpty()) {
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ class CommonRoomsModel : public QAbstractListModel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
RoomIdRole = Qt::DisplayRole,
|
RoomIdRole = Qt::UserRole,
|
||||||
|
RoomNameRole,
|
||||||
|
RoomAvatarRole,
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles)
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
@@ -39,6 +41,8 @@ public:
|
|||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||||
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
|
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
|
||||||
|
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
void userIdChanged();
|
void userIdChanged();
|
||||||
|
|||||||
41
src/app/models/limitermodel.cpp
Normal file
41
src/app/models/limitermodel.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "models/limitermodel.h"
|
||||||
|
|
||||||
|
LimiterModel::LimiterModel(QObject *parent)
|
||||||
|
: QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
|
||||||
|
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
|
||||||
|
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
int LimiterModel::maximumCount() const
|
||||||
|
{
|
||||||
|
return m_maximumCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LimiterModel::setMaximumCount(int maximumCount)
|
||||||
|
{
|
||||||
|
if (m_maximumCount != maximumCount) {
|
||||||
|
m_maximumCount = maximumCount;
|
||||||
|
Q_EMIT maximumCountChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int LimiterModel::extraCount() const
|
||||||
|
{
|
||||||
|
if (sourceModel()) {
|
||||||
|
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(source_parent)
|
||||||
|
return source_row < maximumCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_limitermodel.cpp"
|
||||||
41
src/app/models/limitermodel.h
Normal file
41
src/app/models/limitermodel.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class LimiterModel
|
||||||
|
*
|
||||||
|
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
|
||||||
|
*
|
||||||
|
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
|
||||||
|
*/
|
||||||
|
class LimiterModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
|
||||||
|
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LimiterModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] int maximumCount() const;
|
||||||
|
void setMaximumCount(int maximumCount);
|
||||||
|
|
||||||
|
[[nodiscard]] int extraCount() const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void maximumCountChanged();
|
||||||
|
void extraCountChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_maximumCount = 0;
|
||||||
|
};
|
||||||
@@ -259,7 +259,7 @@ Comment[sa]=कक्षस्य नूतनं निमन्त्रणम
|
|||||||
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]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
||||||
Comment[tr]=Bir odaya yeni bir davetiye var
|
Comment[tr]=Bir odaya yeni bir davet var
|
||||||
Comment[uk]=У кімнаті нове запрошення
|
Comment[uk]=У кімнаті нове запрошення
|
||||||
Comment[zh_CN]=有新的聊天室邀请
|
Comment[zh_CN]=有新的聊天室邀请
|
||||||
Comment[zh_TW]=有新的加入聊天室邀請
|
Comment[zh_TW]=有新的加入聊天室邀請
|
||||||
@@ -287,6 +287,7 @@ Name[ia]=Comparti
|
|||||||
Name[it]=Condivisione
|
Name[it]=Condivisione
|
||||||
Name[ka]=გაზიარება
|
Name[ka]=გაზიარება
|
||||||
Name[ko]=공유
|
Name[ko]=공유
|
||||||
|
Name[lt]=Bendrinti
|
||||||
Name[lv]=Kopīgot
|
Name[lv]=Kopīgot
|
||||||
Name[nl]=Gedeelde
|
Name[nl]=Gedeelde
|
||||||
Name[nn]=Del
|
Name[nn]=Del
|
||||||
@@ -322,6 +323,7 @@ Comment[ia]=Le exito de compartir un pecietta de contento
|
|||||||
Comment[it]=Il risultato della condivisione di un contenuto
|
Comment[it]=Il risultato della condivisione di un contenuto
|
||||||
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
||||||
Comment[ko]=콘텐츠 공유 결과
|
Comment[ko]=콘텐츠 공유 결과
|
||||||
|
Comment[lt]=Turinio dalies bendrinimo rezultatas
|
||||||
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[nn]=Resultatet av deling av innhald
|
||||||
|
|||||||
@@ -211,10 +211,6 @@
|
|||||||
<label>Enable threads</label>
|
<label>Enable threads</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="SecretBackup" type="bool">
|
|
||||||
<label>Enable secret backup</label>
|
|
||||||
<default>false</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="Phone3PId" type="bool">
|
<entry name="Phone3PId" type="bool">
|
||||||
<label>Enable add phone numbers as 3PIDs</label>
|
<label>Enable add phone numbers as 3PIDs</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
|
|||||||
@@ -216,12 +216,12 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notification->setTitle(room->displayName());
|
||||||
|
|
||||||
QString entry;
|
QString entry;
|
||||||
if (sender == room->displayName()) {
|
if (room->isDirectChat()) {
|
||||||
notification->setTitle(sender);
|
|
||||||
entry = text.toHtmlEscaped();
|
entry = text.toHtmlEscaped();
|
||||||
} else {
|
} else {
|
||||||
notification->setTitle(room->displayName());
|
|
||||||
entry = i18n("%1: %2", sender, text.toHtmlEscaped());
|
entry = i18n("%1: %2", sender, text.toHtmlEscaped());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
notification->setReplyAction(std::move(replyAction));
|
notification->setReplyAction(std::move(replyAction));
|
||||||
}
|
}
|
||||||
|
|
||||||
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
if (Controller::instance().accounts()->rowCount() > 1) {
|
||||||
|
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
||||||
|
}
|
||||||
notification->sendEvent();
|
notification->sendEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +349,9 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
|
|||||||
m_invitations.remove(room->id());
|
m_invitations.remove(room->id());
|
||||||
});
|
});
|
||||||
|
|
||||||
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
if (Controller::instance().accounts()->rowCount() > 1) {
|
||||||
|
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
||||||
|
}
|
||||||
|
|
||||||
notification->sendEvent();
|
notification->sendEvent();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu", "Open Secret Backup")
|
text: i18nc("@action:inmenu", "Open Secret Backup")
|
||||||
icon.name: "unlock"
|
icon.name: "unlock"
|
||||||
visible: NeoChatConfig.secretBackup
|
|
||||||
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog'), {}, {
|
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog'), {}, {
|
||||||
title: i18nc("@title:window", "Open Key Backup")
|
title: i18nc("@title:window", "Open Key Backup")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,14 +45,12 @@ Labs.MenuBar {
|
|||||||
}
|
}
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
icon.name: "compass-symbolic"
|
icon.name: "compass-symbolic"
|
||||||
text: i18nc("@action:inmenu", "Explore Rooms")
|
text: i18nc("@action:inmenu Explore public rooms and spaces", "Explore")
|
||||||
enabled: root.connection
|
enabled: root.connection
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
}, {
|
}, {});
|
||||||
title: i18nc("@title", "Explore Rooms")
|
|
||||||
});
|
|
||||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||||
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,16 +27,17 @@ ColumnLayout {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
KirigamiComponents.Avatar {
|
KirigamiComponents.AvatarButton {
|
||||||
id: avatar
|
id: avatar
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
name: root.invitingMember.displayName
|
name: root.invitingMember.displayName
|
||||||
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
|
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
|
||||||
color: root.invitingMember.color
|
color: root.invitingMember.color
|
||||||
|
|
||||||
|
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -54,6 +55,12 @@ ColumnLayout {
|
|||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
text: root.currentRoom.displayName
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.SelectableLabel {
|
Kirigami.SelectableLabel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
font: Kirigami.Theme.smallFont
|
font: Kirigami.Theme.smallFont
|
||||||
@@ -61,12 +68,7 @@ ColumnLayout {
|
|||||||
visible: root.currentRoom && root.currentRoom.canonicalAlias
|
visible: root.currentRoom && root.currentRoom.canonicalAlias
|
||||||
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
|
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
|
||||||
color: Kirigami.Theme.disabledTextColor
|
color: Kirigami.Theme.disabledTextColor
|
||||||
}
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|
||||||
Kirigami.Heading {
|
|
||||||
text: root.currentRoom.displayName
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,8 +139,24 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
|
id: viewProfileDelegate
|
||||||
|
|
||||||
|
icon.name: "user-properties-symbolic"
|
||||||
|
text: i18nc("@action:button View this user's profile", "View %1's Profile", root.invitingMember.displayName)
|
||||||
|
|
||||||
|
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {
|
||||||
|
above: viewProfileDelegate
|
||||||
|
below: ignoreUserDelegate
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
id: ignoreUserDelegate
|
||||||
|
|
||||||
icon.name: "list-remove-symbolic"
|
icon.name: "list-remove-symbolic"
|
||||||
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
|
text: i18nc("@action:button Ignore the user", "Ignore %1 and Reject Invite", root.invitingMember.displayName)
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.currentRoom.forget()
|
root.currentRoom.forget()
|
||||||
|
|||||||
@@ -23,72 +23,63 @@ Kirigami.Page {
|
|||||||
name: "cancelled"
|
name: "cancelled"
|
||||||
when: root.session.state === KeyVerificationSession.CANCELED
|
when: root.session.state === KeyVerificationSession.CANCELED
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: verificationCanceled
|
||||||
sourceComponent: verificationCanceled
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "waitingForVerification"
|
name: "waitingForVerification"
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
|
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: emojiSas
|
||||||
sourceComponent: emojiSas
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "waitingForReady"
|
name: "waitingForReady"
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
|
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: message
|
||||||
sourceComponent: message
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "incoming"
|
name: "incoming"
|
||||||
when: root.session.state === KeyVerificationSession.INCOMING
|
when: root.session.state === KeyVerificationSession.INCOMING
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: message
|
||||||
sourceComponent: message
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "waitingForKey"
|
name: "waitingForKey"
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
|
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: message
|
||||||
sourceComponent: message
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "waitingForAccept"
|
name: "waitingForAccept"
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
|
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: message
|
||||||
sourceComponent: message
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "waitingForMac"
|
name: "waitingForMac"
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
|
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: message
|
||||||
sourceComponent: message
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "ready"
|
name: "ready"
|
||||||
when: root.session.state === KeyVerificationSession.READY
|
when: root.session.state === KeyVerificationSession.READY
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: chooseVerificationComponent
|
||||||
sourceComponent: chooseVerificationComponent
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "done"
|
name: "done"
|
||||||
when: root.session.state === KeyVerificationSession.DONE
|
when: root.session.state === KeyVerificationSession.DONE
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: stateLoader
|
stateLoader.sourceComponent: message
|
||||||
sourceComponent: message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -23,13 +23,45 @@ Components.AlbumMaximizeComponent {
|
|||||||
*/
|
*/
|
||||||
required property NeoChatRoom currentRoom
|
required property NeoChatRoom currentRoom
|
||||||
|
|
||||||
readonly property string currentEventId: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.EventIdRole)
|
readonly property string currentEventId: {
|
||||||
|
const idx = (content as ListView).currentIndex;
|
||||||
|
|
||||||
readonly property var currentAuthor: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.AuthorRole)
|
if (idx === -1) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var currentTime: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.TimeRole)
|
return model.data(model.index(idx, 0), TimelineMessageModel.EventIdRole)
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var currentProgressInfo: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
|
readonly property var currentAuthor: {
|
||||||
|
const idx = (content as ListView).currentIndex;
|
||||||
|
|
||||||
|
if (idx === -1) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.data(model.index(idx, 0), TimelineMessageModel.AuthorRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var currentTime: {
|
||||||
|
|
||||||
|
const idx = (content as ListView).currentIndex;
|
||||||
|
|
||||||
|
if (idx === -1) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.data(model.index(idx, 0), TimelineMessageModel.TimeRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var currentProgressInfo: {
|
||||||
|
const idx = (content as ListView).currentIndex;
|
||||||
|
|
||||||
|
if (idx === -1) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
model.data(model.index(idx, 0), TimelineMessageModel.ProgressInfoRole)
|
||||||
|
}
|
||||||
|
|
||||||
actions: [
|
actions: [
|
||||||
ShareAction {
|
ShareAction {
|
||||||
@@ -122,7 +154,10 @@ Components.AlbumMaximizeComponent {
|
|||||||
|
|
||||||
onOpened: forceActiveFocus()
|
onOpened: forceActiveFocus()
|
||||||
|
|
||||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
|
onItemRightClicked: {
|
||||||
|
const event = root.currentRoom.findEvent(root.currentEventId);
|
||||||
|
RoomManager.viewEventMenu(root.QQC2.Overlay.overlay, event, root.currentRoom)
|
||||||
|
}
|
||||||
|
|
||||||
onSaveItem: {
|
onSaveItem: {
|
||||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay) as Dialogs.FileDialog;
|
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay) as Dialogs.FileDialog;
|
||||||
|
|||||||
@@ -30,15 +30,13 @@ Kirigami.SearchDialog {
|
|||||||
emptyText: i18nc("Placeholder message", "No room found")
|
emptyText: i18nc("Placeholder message", "No room found")
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
id: exploreRoomAction
|
id: exploreRoomAction
|
||||||
text: i18nc("@action:button", "Explore rooms")
|
text: i18nc("@action:button Explore public rooms and spaces", "Explore")
|
||||||
icon.name: "compass"
|
icon.name: "compass"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.close()
|
root.close()
|
||||||
let dialog = root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
let dialog = root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
}, {
|
}, {});
|
||||||
title: i18nc("@title", "Explore Rooms")
|
|
||||||
});
|
|
||||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||||
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Kirigami.Page {
|
|||||||
Keys.onReturnPressed: event => {
|
Keys.onReturnPressed: event => {
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
root.accepted(reason.text);
|
root.accepted(reason.text);
|
||||||
root.closeDialog();
|
root.Kirigami.PageStack.closeDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +52,14 @@ Kirigami.Page {
|
|||||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.accepted(reason.text);
|
root.accepted(reason.text);
|
||||||
root.closeDialog();
|
root.Kirigami.PageStack.closeDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
icon.name: "dialog-cancel-symbolic"
|
icon.name: "dialog-cancel-symbolic"
|
||||||
text: i18nc("@action", "Cancel")
|
text: i18nc("@action", "Cancel")
|
||||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
||||||
onClicked: root.closeDialog()
|
onClicked: root.Kirigami.PageStack.closeDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).wideMode
|
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow)?.wideMode
|
||||||
icon.name: "view-right-new"
|
icon.name: "view-right-new"
|
||||||
onTriggered: (root.QQC2.ApplicationWindow.window as Main).openRoomDrawer()
|
onTriggered: (root.QQC2.ApplicationWindow.window as Main).openRoomDrawer()
|
||||||
}
|
}
|
||||||
@@ -227,6 +227,8 @@ Kirigami.Page {
|
|||||||
// Used to keep track of messages so we can hide the right one at the right time
|
// Used to keep track of messages so we can hide the right one at the right time
|
||||||
property string messageId
|
property string messageId
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
showCloseButton: true
|
showCloseButton: true
|
||||||
visible: false
|
visible: false
|
||||||
position: Kirigami.InlineMessage.Position.Header
|
position: Kirigami.InlineMessage.Position.Header
|
||||||
@@ -253,7 +255,6 @@ Kirigami.Page {
|
|||||||
id: timelineView
|
id: timelineView
|
||||||
messageFilterModel: root.messageFilterModel
|
messageFilterModel: root.messageFilterModel
|
||||||
compactLayout: NeoChatConfig.compactLayout
|
compactLayout: NeoChatConfig.compactLayout
|
||||||
fileDropEnabled: !Controller.isFlatpak
|
|
||||||
markReadCondition: NeoChatConfig.markReadCondition
|
markReadCondition: NeoChatConfig.markReadCondition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,7 +289,7 @@ Kirigami.Page {
|
|||||||
|
|
||||||
footer: Loader {
|
footer: Loader {
|
||||||
id: chatBarLoader
|
id: chatBarLoader
|
||||||
height: active ? (item as ChatBar).implicitHeight : 0
|
height: active ? (item as ChatBar)?.implicitHeight : 0
|
||||||
active: timelineViewLoader.active && !root.currentRoom.readOnly
|
active: timelineViewLoader.active && !root.currentRoom.readOnly
|
||||||
sourceComponent: ChatBar {
|
sourceComponent: ChatBar {
|
||||||
id: chatBar
|
id: chatBar
|
||||||
@@ -348,14 +349,16 @@ Kirigami.Page {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowDelegateMenu(eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) {
|
function onShowDelegateMenu(parent: QtObject, eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, selectedText: string, hoveredLink: string) {
|
||||||
(delegateContextMenu.createObject(root, {
|
(delegateContextMenu.createObject(parent, {
|
||||||
author: author,
|
author: author,
|
||||||
eventId: eventId,
|
eventId: eventId,
|
||||||
plainText: plainText,
|
plainText: plainText,
|
||||||
mimeType: mimeType,
|
mimeType: mimeType,
|
||||||
progressInfo: progressInfo,
|
progressInfo: progressInfo,
|
||||||
messageComponentType: messageComponentType,
|
messageComponentType: messageComponentType,
|
||||||
|
selectedText,
|
||||||
|
hoveredLink,
|
||||||
}) as DelegateContextMenu).popup();
|
}) as DelegateContextMenu).popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
90
src/app/qml/SeenByDialog.qml
Normal file
90
src/app/qml/SeenByDialog.qml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||||
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Kirigami.Dialog {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var model
|
||||||
|
|
||||||
|
standardButtons: Kirigami.Dialog.NoButton
|
||||||
|
|
||||||
|
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
||||||
|
maximumHeight: Kirigami.Units.gridUnit * 24
|
||||||
|
title: i18nc("@title:menu Seen by/read marker dialog", "Seen By")
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
model: root.model
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
onCountChanged: {
|
||||||
|
if (listView.count === 0) {
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Delegates.RoundedItemDelegate {
|
||||||
|
id: userDelegate
|
||||||
|
|
||||||
|
required property string displayName
|
||||||
|
required property url avatarUrl
|
||||||
|
required property color memberColor
|
||||||
|
required property string userId
|
||||||
|
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
text: displayName
|
||||||
|
highlighted: false
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
root.close();
|
||||||
|
RoomManager.resolveResource(userDelegate.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
KirigamiComponents.Avatar {
|
||||||
|
implicitWidth: height
|
||||||
|
sourceSize {
|
||||||
|
height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
}
|
||||||
|
source: userDelegate.avatarUrl
|
||||||
|
name: userDelegate.displayName
|
||||||
|
color: userDelegate.memberColor
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
text: userDelegate.displayName
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
clip: true // Intentional to limit insane Unicode in display names
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import org.kde.neochat
|
|||||||
FormCard.FormCardPage {
|
FormCard.FormCardPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool processing: false
|
||||||
|
|
||||||
title: i18nc("@title:window", "Load your encrypted messages")
|
title: i18nc("@title:window", "Load your encrypted messages")
|
||||||
|
|
||||||
topPadding: Kirigami.Units.gridUnit
|
topPadding: Kirigami.Units.gridUnit
|
||||||
@@ -25,75 +27,42 @@ FormCard.FormCardPage {
|
|||||||
position: Kirigami.InlineMessage.Position.Header
|
position: Kirigami.InlineMessage.Position.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
property SSSSHandler ssssHandler: SSSSHandler {
|
Connections {
|
||||||
id: ssssHandler
|
target: Controller.activeConnection
|
||||||
|
function onKeyBackupError(): void {
|
||||||
|
securityKeyField.clear()
|
||||||
|
root.processing = false
|
||||||
|
banner.text = i18nc("@info:status", "The security key or backup passphrase was not correct.")
|
||||||
|
banner.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
property bool processing: false
|
function onKeyBackupUnlocked(): void {
|
||||||
|
root.processing = false
|
||||||
connection: Controller.activeConnection
|
|
||||||
onKeyBackupUnlocked: {
|
|
||||||
ssssHandler.processing = false
|
|
||||||
banner.text = i18nc("@info:status", "Encryption keys restored.")
|
banner.text = i18nc("@info:status", "Encryption keys restored.")
|
||||||
banner.type = Kirigami.MessageType.Positive
|
banner.type = Kirigami.MessageType.Positive
|
||||||
banner.visible = true
|
banner.visible = true
|
||||||
}
|
}
|
||||||
onError: error => {
|
|
||||||
if (error !== SSSSHandler.WrongKeyError) {
|
|
||||||
banner.text = error
|
|
||||||
banner.visible = true
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
passwordField.clear()
|
|
||||||
ssssHandler.processing = false
|
|
||||||
banner.text = i18nc("@info:status", "The security phrase was not correct.")
|
|
||||||
banner.visible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
title: i18nc("@title", "Unlock using Passphrase")
|
title: i18nc("@title", "Unlock using Security Key or Backup Passphrase")
|
||||||
}
|
}
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
FormCard.FormTextDelegate {
|
FormCard.FormTextDelegate {
|
||||||
description: i18nc("@info", "If you have a backup passphrase for this account, enter it below.")
|
description: i18nc("@info", "If you have a security key or backup passphrase for this account, enter it below or upload it as a file.")
|
||||||
}
|
|
||||||
FormCard.FormTextFieldDelegate {
|
|
||||||
id: passwordField
|
|
||||||
label: i18nc("@label:textbox", "Backup Password:")
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
}
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
id: unlockButton
|
|
||||||
text: i18nc("@action:button", "Unlock")
|
|
||||||
icon.name: "unlock"
|
|
||||||
enabled: passwordField.text.length > 0 && !ssssHandler.processing
|
|
||||||
onClicked: {
|
|
||||||
ssssHandler.processing = true
|
|
||||||
banner.visible = false
|
|
||||||
ssssHandler.unlockSSSSWithPassphrase(passwordField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormHeader {
|
|
||||||
title: i18nc("@title", "Unlock using Security Key")
|
|
||||||
}
|
|
||||||
FormCard.FormCard {
|
|
||||||
FormCard.FormTextDelegate {
|
|
||||||
description: i18nc("@info", "If you have a security key for this account, enter it below or upload it as a file.")
|
|
||||||
}
|
}
|
||||||
FormCard.FormTextFieldDelegate {
|
FormCard.FormTextFieldDelegate {
|
||||||
id: securityKeyField
|
id: securityKeyField
|
||||||
label: i18nc("@label:textbox", "Security Key:")
|
label: i18nc("@label:textbox", "Security Key or Backup Passphrase:")
|
||||||
echoMode: TextInput.Password
|
echoMode: TextInput.Password
|
||||||
}
|
}
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
id: uploadSecurityKeyButton
|
id: uploadSecurityKeyButton
|
||||||
text: i18nc("@action:button", "Upload from File")
|
text: i18nc("@action:button", "Upload from File")
|
||||||
icon.name: "cloud-upload"
|
icon.name: "cloud-upload"
|
||||||
enabled: !ssssHandler.processing
|
enabled: !root.processing
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ssssHandler.processing = true
|
root.processing = true
|
||||||
openFileDialog.open()
|
openFileDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,10 +70,10 @@ FormCard.FormCardPage {
|
|||||||
id: unlockSecurityKeyButton
|
id: unlockSecurityKeyButton
|
||||||
text: i18nc("@action:button", "Unlock")
|
text: i18nc("@action:button", "Unlock")
|
||||||
icon.name: "unlock"
|
icon.name: "unlock"
|
||||||
enabled: securityKeyField.text.length > 0 && !ssssHandler.processing
|
enabled: securityKeyField.text.length > 0 && !root.processing
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ssssHandler.processing = true
|
root.processing = true
|
||||||
ssssHandler.unlockSSSSFromSecurityKey(securityKeyField.text)
|
Controller.activeConnection.unlockSSSS(securityKeyField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,10 +89,10 @@ FormCard.FormCardPage {
|
|||||||
id: unlockCrossSigningButton
|
id: unlockCrossSigningButton
|
||||||
icon.name: "emblem-shared-symbolic"
|
icon.name: "emblem-shared-symbolic"
|
||||||
text: i18nc("@action:button", "Request from other Devices")
|
text: i18nc("@action:button", "Request from other Devices")
|
||||||
enabled: !ssssHandler.processing
|
enabled: !root.processing
|
||||||
onClicked: {
|
onClicked: {
|
||||||
ssssHandler.processing = true
|
root.processing = true
|
||||||
ssssHandler.unlockSSSSFromCrossSigning()
|
Controller.activeConnection.unlockSSSS("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,37 +20,62 @@ Kirigami.Dialog {
|
|||||||
// Make sure that code is prepared to deal with this property being null
|
// Make sure that code is prepared to deal with this property being null
|
||||||
property NeoChatRoom room
|
property NeoChatRoom room
|
||||||
property var user
|
property var user
|
||||||
|
// Used for the "View Main Profile" feature so you can toggle back to the room profile.
|
||||||
|
property NeoChatRoom oldRoom
|
||||||
|
|
||||||
property NeoChatConnection connection
|
property NeoChatConnection connection
|
||||||
|
|
||||||
leftPadding: 0
|
property CommonRoomsModel model: CommonRoomsModel {
|
||||||
rightPadding: 0
|
connection: root.connection
|
||||||
topPadding: 0
|
userId: root.user.id
|
||||||
bottomPadding: 0
|
}
|
||||||
|
|
||||||
|
property LimiterModel limiterModel: LimiterModel {
|
||||||
|
maximumCount: 5
|
||||||
|
sourceModel: root.model
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool isSelf: root.user.id === root.connection.localUserId
|
||||||
|
readonly property bool hasMutualRooms: root.model.count > 0
|
||||||
|
readonly property bool isRoomProfile: root.room
|
||||||
|
readonly property string shareUrl: "https://matrix.to/#/" + root.user.id
|
||||||
|
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing * 2
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing * 2
|
||||||
|
topPadding: Kirigami.Units.largeSpacing * 2
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing * 2
|
||||||
|
|
||||||
standardButtons: Kirigami.Dialog.NoButton
|
standardButtons: Kirigami.Dialog.NoButton
|
||||||
|
|
||||||
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
||||||
title: i18nc("@title:menu Account details dialog", "Account Details")
|
title: i18nc("@title:menu Account details dialog", "Account Details")
|
||||||
|
|
||||||
|
header: null
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: detailRow
|
id: detailRow
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
KirigamiComponents.Avatar {
|
KirigamiComponents.Avatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||||
|
|
||||||
name: root.room ? root.room.member(root.user.id).displayName : root.user.displayName
|
name: root.room ? root.room.member(root.user.id).displayName : root.user.displayName
|
||||||
source: root.room ? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
|
source: {
|
||||||
|
if (root.room) {
|
||||||
|
return root.room.member(root.user.id).avatarUrl;
|
||||||
|
} else if(root.user.avatarUrl.toString() !== '') {
|
||||||
|
return root.connection.makeMediaUrl(root.user.avatarUrl);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
color: root.room ? root.room.member(root.user.id).color : QmlUtils.getUserColor(root.user.hueF)
|
color: root.room ? root.room.member(root.user.id).color : QmlUtils.getUserColor(root.user.hueF)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,223 +100,355 @@ Kirigami.Dialog {
|
|||||||
id: idLabel
|
id: idLabel
|
||||||
textFormat: TextEdit.PlainText
|
textFormat: TextEdit.PlainText
|
||||||
text: idLabelTextMetrics.elidedText
|
text: idLabelTextMetrics.elidedText
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
id: idLabelTextMetrics
|
id: idLabelTextMetrics
|
||||||
text: root.user.id
|
text: root.user.id
|
||||||
elide: Qt.ElideRight
|
elide: Qt.ElideRight
|
||||||
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
|
elideWidth: root.availableWidth - avatar.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Label {
|
Kirigami.ActionToolBar {
|
||||||
property CommonRoomsModel model: CommonRoomsModel {
|
|
||||||
connection: root.connection
|
|
||||||
userId: root.user.id
|
|
||||||
}
|
|
||||||
|
|
||||||
text: i18ncp("@info", "One mutual room", "%1 mutual rooms", model.count)
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
visible: model.count > 0
|
|
||||||
|
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:intoolbar Message this user directly", "Message")
|
||||||
|
icon.name: "document-send-symbolic"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.close();
|
||||||
|
root.connection.requestDirectChat(root.user.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
icon.name: "im-invisible-user-symbolic"
|
||||||
|
text: root.connection.isIgnored(root.user.id) ? i18nc("@action:intoolbar Unignore or 'unblock' this user", "Unignore") : i18nc("@action:intoolbar Ignore or 'block' this user", "Ignore")
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.close();
|
||||||
|
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:intoolbar Copy shareable link for this user", "Copy Link")
|
||||||
|
icon.name: "username-copy-symbolic"
|
||||||
|
|
||||||
|
onTriggered: Clipboard.saveText(root.shareUrl)
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:intoolbar Search for this user's messages.", "Search Messages…")
|
||||||
|
icon.name: "search-symbolic"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
|
||||||
|
room: root.room,
|
||||||
|
senderId: root.user.id
|
||||||
|
}, {
|
||||||
|
title: i18nc("@action:title", "Search")
|
||||||
|
});
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:intoolbar", "Show QR Code")
|
||||||
|
icon.name: "view-barcode-qr-symbolic"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||||
|
text: root.shareUrl,
|
||||||
|
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
|
||||||
|
subtitle: root.user.id,
|
||||||
|
avatarColor: root.room?.member(root.user.id).color,
|
||||||
|
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
|
||||||
|
}) as QrCodeMaximizeComponent;
|
||||||
|
root.close();
|
||||||
|
qrCode.open();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…")
|
||||||
|
icon.name: "dialog-warning-symbolic"
|
||||||
|
visible: root.connection.supportsMatrixSpecVersion("v1.13")
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||||
|
title: i18nc("@title:dialog", "Report User"),
|
||||||
|
placeholder: i18nc("@info:placeholder", "Reason for reporting this user"),
|
||||||
|
icon: "dialog-warning-symbolic",
|
||||||
|
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report")
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title", "Report User"),
|
||||||
|
width: Kirigami.Units.gridUnit * 25
|
||||||
|
}) as ReasonDialog;
|
||||||
|
dialog.accepted.connect(reason => {
|
||||||
|
root.connection.reportUser(root.user.id, reason);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: root.room
|
||||||
|
|
||||||
|
text: i18nc("@action:button", "View Main Profile")
|
||||||
|
icon.name: "user-properties-symbolic"
|
||||||
|
onTriggered: {
|
||||||
|
root.oldRoom = root.room;
|
||||||
|
root.room = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: !root.room && root.oldRoom
|
||||||
|
|
||||||
|
text: i18nc("@action:button", "View Room Profile")
|
||||||
|
icon.name: "user-properties-symbolic"
|
||||||
|
onTriggered: {
|
||||||
|
root.room = root.oldRoom;
|
||||||
|
root.oldRoom = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.AbstractButton {
|
}
|
||||||
id: qrButton
|
|
||||||
Layout.minimumHeight: avatar.height * 0.75
|
|
||||||
Layout.maximumHeight: avatar.height * 1.5
|
|
||||||
Layout.maximumWidth: avatar.height * 1.5
|
|
||||||
|
|
||||||
contentItem: Barcode {
|
Kirigami.Heading {
|
||||||
id: barcode
|
text: i18nc("@title Moderation actions for this user", "Moderation")
|
||||||
barcodeType: Barcode.QRCode
|
level: 2
|
||||||
content: "https://matrix.to/#/" + root.user.id
|
visible: root.isRoomProfile && moderationToolbar.actions.filter(function (it) { return it.visible; }).length > 0
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
}
|
||||||
text: barcode.content,
|
|
||||||
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
|
Kirigami.ActionToolBar {
|
||||||
subtitle: root.user.id,
|
id: moderationToolbar
|
||||||
avatarColor: root.room?.member(root.user.id).color,
|
|
||||||
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
|
flat: false
|
||||||
}) as QrCodeMaximizeComponent;
|
visible: root.isRoomProfile
|
||||||
root.close();
|
|
||||||
qrCode.open();
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: {
|
||||||
|
if (root.room) {
|
||||||
|
return !root.isSelf && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
text: i18nc("@action:button Kick the user from the room", "Kick…")
|
||||||
|
icon.name: "im-kick-user"
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||||
|
title: i18nc("@title:dialog", "Kick User"),
|
||||||
|
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
|
||||||
|
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
|
||||||
|
icon: "im-kick-user"
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title:dialog", "Kick User"),
|
||||||
|
width: Kirigami.Units.gridUnit * 25
|
||||||
|
});
|
||||||
|
dialog.accepted.connect(reason => {
|
||||||
|
root.room.kickMember(root.user.id, reason);
|
||||||
|
});
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: {
|
||||||
|
if (root.room) {
|
||||||
|
return !root.isSelf && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
text: i18nc("@action:button Ban this user from the room", "Ban…")
|
||||||
|
icon.name: "im-ban-user"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||||
|
title: i18nc("@title:dialog", "Ban User"),
|
||||||
|
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
|
||||||
|
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
|
||||||
|
icon: "im-ban-user"
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title:dialog", "Ban User"),
|
||||||
|
width: Kirigami.Units.gridUnit * 25
|
||||||
|
});
|
||||||
|
dialog.accepted.connect(reason => {
|
||||||
|
root.room.ban(root.user.id, reason);
|
||||||
|
});
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: {
|
||||||
|
if (root.room) {
|
||||||
|
return !root.isSelf && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
text: i18nc("@action:button Unban the user from this room", "Unban")
|
||||||
|
icon.name: "im-irc"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
onTriggered: {
|
||||||
|
root.room.unban(root.user.id);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: (root.user.id === root.connection.localUserId || (root.room?.canSendState("redact") ?? false))
|
||||||
|
|
||||||
|
text: i18nc("@action:button Remove messages from the user in this room", "Remove Messages…")
|
||||||
|
icon.name: "delete"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||||
|
title: i18nc("@title:dialog", "Remove Messages"),
|
||||||
|
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
|
||||||
|
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
|
||||||
|
icon: "delete"
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title", "Remove Messages"),
|
||||||
|
width: Kirigami.Units.gridUnit * 25
|
||||||
|
});
|
||||||
|
dialog.accepted.connect(reason => {
|
||||||
|
root.room.deleteMessagesByUser(root.user.id, reason);
|
||||||
|
});
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
text: i18nc("@title Role such as 'Admin' or 'Moderator' for this user", "Power Level")
|
||||||
|
level: 2
|
||||||
|
visible: root.isRoomProfile
|
||||||
|
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
visible: root.isRoomProfile
|
||||||
|
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
visible: {
|
||||||
|
if (root.room) {
|
||||||
|
return root.room.canSendState("m.room.power_levels") && !(root.room.roomCreatorHasUltimatePowerLevel() && root.room.isCreator(root.user.id));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
text: i18nc("@action:button Set the power level (such as 'Admin') for this user", "Set Power Level")
|
||||||
|
icon.name: "document-edit-symbolic"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.text: barcode.content
|
QQC2.ToolTip.text: text
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Chip {
|
onClicked: {
|
||||||
visible: root.room
|
(powerLevelDialog.createObject(this, {
|
||||||
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
|
room: root.room,
|
||||||
closable: false
|
userId: root.user.id,
|
||||||
checkable: false
|
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
|
||||||
|
}) as PowerLevelDialog).open();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
Component {
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
visible: root.user.id !== root.connection.localUserId && !!root.user
|
|
||||||
text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user")
|
|
||||||
icon.name: "im-invisible-user"
|
|
||||||
onClicked: {
|
|
||||||
root.close();
|
|
||||||
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
|
|
||||||
|
|
||||||
text: i18nc("@action:button", "Kick this user")
|
|
||||||
icon.name: "im-kick-user"
|
|
||||||
onClicked: {
|
|
||||||
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
|
||||||
title: i18nc("@title:dialog", "Kick User"),
|
|
||||||
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
|
|
||||||
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
|
|
||||||
icon: "im-kick-user"
|
|
||||||
}, {
|
|
||||||
title: i18nc("@title:dialog", "Kick User"),
|
|
||||||
width: Kirigami.Units.gridUnit * 25
|
|
||||||
});
|
|
||||||
dialog.accepted.connect(reason => {
|
|
||||||
root.room.kickMember(root.user.id, reason);
|
|
||||||
});
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("invite") && !root.room.containsUser(root.user.id)
|
|
||||||
|
|
||||||
enabled: root.room && !root.room.isUserBanned(root.user.id)
|
|
||||||
text: i18nc("@action:button", "Invite this user")
|
|
||||||
icon.name: "list-add-user"
|
|
||||||
onClicked: {
|
|
||||||
root.room.inviteToRoom(root.user.id);
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
|
|
||||||
|
|
||||||
text: i18nc("@action:button", "Ban this user")
|
|
||||||
icon.name: "im-ban-user"
|
|
||||||
icon.color: Kirigami.Theme.negativeTextColor
|
|
||||||
onClicked: {
|
|
||||||
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
|
||||||
title: i18nc("@title:dialog", "Ban User"),
|
|
||||||
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
|
|
||||||
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
|
|
||||||
icon: "im-ban-user"
|
|
||||||
}, {
|
|
||||||
title: i18nc("@title:dialog", "Ban User"),
|
|
||||||
width: Kirigami.Units.gridUnit * 25
|
|
||||||
});
|
|
||||||
dialog.accepted.connect(reason => {
|
|
||||||
root.room.ban(root.user.id, reason);
|
|
||||||
});
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id)
|
|
||||||
|
|
||||||
text: i18nc("@action:button", "Unban this user")
|
|
||||||
icon.name: "im-irc"
|
|
||||||
icon.color: Kirigami.Theme.negativeTextColor
|
|
||||||
onClicked: {
|
|
||||||
root.room.unban(root.user.id);
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
visible: root.room && root.room.canSendState("m.room.power_levels")
|
|
||||||
text: i18nc("@action:button", "Set user power level")
|
|
||||||
icon.name: "visibility"
|
|
||||||
onClicked: {
|
|
||||||
(powerLevelDialog.createObject(this, {
|
|
||||||
room: root.room,
|
|
||||||
userId: root.user.id,
|
|
||||||
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
|
|
||||||
}) as PowerLevelDialog).open();
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: powerLevelDialog
|
|
||||||
PowerLevelDialog {
|
|
||||||
id: powerLevelDialog
|
id: powerLevelDialog
|
||||||
|
PowerLevelDialog {
|
||||||
|
id: powerLevelDialog
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
Kirigami.Heading {
|
||||||
visible: root.room && (root.user.id === root.connection.localUserId || root.room.canSendState("redact"))
|
text: i18nc("@title The set of common rooms between your current user and the one shown", "Mutual Rooms")
|
||||||
|
level: 4
|
||||||
|
visible: !root.isSelf && root.hasMutualRooms
|
||||||
|
|
||||||
text: i18nc("@action:button", "Remove recent messages by this user")
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
icon.name: "delete"
|
}
|
||||||
icon.color: Kirigami.Theme.negativeTextColor
|
|
||||||
onClicked: {
|
RowLayout {
|
||||||
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
spacing: Kirigami.Units.smallSpacing
|
||||||
title: i18nc("@title:dialog", "Remove Messages"),
|
visible: !root.isSelf && root.hasMutualRooms
|
||||||
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
|
|
||||||
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
icon: "delete"
|
|
||||||
}, {
|
Repeater {
|
||||||
title: i18nc("@title", "Remove Messages"),
|
model: root.limiterModel
|
||||||
width: Kirigami.Units.gridUnit * 25
|
|
||||||
});
|
delegate: KirigamiComponents.AvatarButton {
|
||||||
dialog.accepted.connect(reason => {
|
required property string roomName
|
||||||
root.room.deleteMessagesByUser(root.user.id, reason);
|
required property string roomAvatar
|
||||||
});
|
required property string roomId
|
||||||
root.close();
|
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||||
|
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||||
|
|
||||||
|
name: roomName
|
||||||
|
source: roomAvatar
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
root.close();
|
||||||
|
RoomManager.resolveResource(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.text: name
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18ncp("@info:label And '%1' more rooms you have in common with this user, but are not shown", "and 1 more…", "and %1 more…", root.limiterModel.extraCount)
|
||||||
|
visible: root.limiterModel.extraCount > 0
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
Kirigami.Heading {
|
||||||
visible: root.user.id !== root.connection.localUserId
|
text: i18nc("@title Private note for this user", "Private Note")
|
||||||
text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat")
|
level: 4
|
||||||
icon.name: "document-send"
|
|
||||||
onClicked: {
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
root.connection.requestDirectChat(root.user.id);
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
QQC2.TextArea {
|
||||||
text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName))
|
id: noteText
|
||||||
icon.name: "search-symbolic"
|
|
||||||
onClicked: {
|
|
||||||
((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
|
|
||||||
room: root.room,
|
|
||||||
senderId: root.user.id
|
|
||||||
}, {
|
|
||||||
title: i18nc("@action:title", "Search")
|
|
||||||
});
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormButtonDelegate {
|
text: root.connection.noteForUser(root.user.id)
|
||||||
text: i18n("Copy link")
|
textFormat: TextEdit.PlainText
|
||||||
icon.name: "username-copy"
|
wrapMode: TextEdit.Wrap
|
||||||
onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id)
|
placeholderText: i18nc("@info:placeholder", "Only visible to you")
|
||||||
|
|
||||||
|
onTextEdited: editTimer.restart()
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
// Prevent unnecessary edits by waiting 1 second
|
||||||
|
Timer {
|
||||||
|
id: editTimer
|
||||||
|
|
||||||
|
interval: 1000
|
||||||
|
onTriggered: root.connection.setNoteForUser(root.user.id, noteText.text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,26 +282,21 @@ void RoomManager::viewEventSource(const QString &eventId)
|
|||||||
Q_EMIT showEventSource(eventId);
|
Q_EMIT showEventSource(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText, const QString &hoveredLink)
|
void RoomManager::viewEventMenu(QObject *parent, const RoomEvent *event, NeoChatRoom *room, const QString &selectedText, const QString &hoveredLink)
|
||||||
{
|
{
|
||||||
if (eventId.isEmpty()) {
|
if (!event) {
|
||||||
qWarning() << "Tried to open event menu with empty event id";
|
qWarning() << "Tried to open event menu with empty event";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto it = room->findInTimeline(eventId);
|
Q_EMIT showDelegateMenu(parent,
|
||||||
if (it == room->historyEdge()) {
|
event->id(),
|
||||||
// This is probably a pending event
|
room->qmlSafeMember(event->senderId()),
|
||||||
return;
|
MessageComponentType::typeForEvent(*event),
|
||||||
}
|
EventHandler::plainBody(room, event),
|
||||||
const auto &event = **it;
|
EventHandler::richBody(room, event),
|
||||||
Q_EMIT showDelegateMenu(eventId,
|
EventHandler::mediaInfo(room, event)["mimeType"_L1].toString(),
|
||||||
room->qmlSafeMember(event.senderId()),
|
room->fileTransferInfo(event->id()),
|
||||||
MessageComponentType::typeForEvent(event),
|
|
||||||
EventHandler::plainBody(room, &event),
|
|
||||||
EventHandler::richBody(room, &event),
|
|
||||||
EventHandler::mediaInfo(room, &event)["mimeType"_L1].toString(),
|
|
||||||
room->fileTransferInfo(eventId),
|
|
||||||
selectedText,
|
selectedText,
|
||||||
hoveredLink);
|
hoveredLink);
|
||||||
}
|
}
|
||||||
@@ -554,6 +549,45 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
|
|||||||
setCurrentRoom({});
|
setCurrentRoom({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString RoomManager::findSpaceIdForCurrentRoom() const
|
||||||
|
{
|
||||||
|
if (!m_currentRoom) {
|
||||||
|
return m_currentSpaceId;
|
||||||
|
}
|
||||||
|
if (m_currentRoom->isDirectChat()) {
|
||||||
|
const auto roomsInSpace = SpaceHierarchyCache::instance().getRoomListForSpace(m_currentSpaceId, false);
|
||||||
|
if (roomsInSpace.contains(m_currentRoom->id())) {
|
||||||
|
return m_currentSpaceId;
|
||||||
|
}
|
||||||
|
return "DM"_L1;
|
||||||
|
}
|
||||||
|
const auto &parentSpaces = SpaceHierarchyCache::instance().parentSpaces(m_currentRoom->id());
|
||||||
|
if (parentSpaces.contains(m_currentSpaceId)) {
|
||||||
|
return m_currentSpaceId;
|
||||||
|
}
|
||||||
|
static auto config = NeoChatConfig::self();
|
||||||
|
if (config->allRoomsInHome()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (const auto &parent = m_connection->room(m_currentRoom->canonicalParent())) {
|
||||||
|
for (const auto &parentParent : SpaceHierarchyCache::instance().parentSpaces(parent->id())) {
|
||||||
|
if (SpaceHierarchyCache::instance().parentSpaces(parentParent).isEmpty()) {
|
||||||
|
return parentParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parent->id();
|
||||||
|
}
|
||||||
|
for (const auto &space : parentSpaces) {
|
||||||
|
if (SpaceHierarchyCache::instance().parentSpaces(space).isEmpty()) {
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_currentRoom->isSpace()) {
|
||||||
|
return m_currentSpaceId;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void RoomManager::setCurrentRoom(const QString &roomId)
|
void RoomManager::setCurrentRoom(const QString &roomId)
|
||||||
{
|
{
|
||||||
if (m_currentRoom != nullptr) {
|
if (m_currentRoom != nullptr) {
|
||||||
@@ -571,57 +605,23 @@ void RoomManager::setCurrentRoom(const QString &roomId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT currentRoomChanged();
|
Q_EMIT currentRoomChanged();
|
||||||
if (m_connection) {
|
|
||||||
if (roomId.isEmpty()) {
|
|
||||||
m_lastRoomConfig.deleteEntry(m_currentSpaceId);
|
|
||||||
} else {
|
|
||||||
// We can't have empty keys in KConfig, so name it "Home"
|
|
||||||
if (m_currentSpaceId.isEmpty()) {
|
|
||||||
m_lastRoomConfig.writeEntry(u"Home"_s, roomId);
|
|
||||||
} else {
|
|
||||||
m_lastRoomConfig.writeEntry(m_currentSpaceId, roomId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (roomId.isEmpty()) {
|
if (roomId.isEmpty()) {
|
||||||
|
m_lastRoomConfig.deleteEntry(m_currentSpaceId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_currentRoom->isSpace()) {
|
|
||||||
return;
|
const auto spaceIdForRoom = findSpaceIdForCurrentRoom();
|
||||||
|
// We can't have empty keys in KConfig, so name it "Home"
|
||||||
|
if (spaceIdForRoom.isEmpty()) {
|
||||||
|
m_lastRoomConfig.writeEntry(u"Home"_s, roomId);
|
||||||
|
} else {
|
||||||
|
m_lastRoomConfig.writeEntry(spaceIdForRoom, roomId);
|
||||||
}
|
}
|
||||||
if (m_currentRoom->isDirectChat()) {
|
|
||||||
const auto roomsInSpace = SpaceHierarchyCache::instance().getRoomListForSpace(m_currentSpaceId, false);
|
if (m_currentSpaceId != spaceIdForRoom) {
|
||||||
if (!roomsInSpace.contains(m_currentRoom->id()) && m_currentSpaceId != "DM"_L1) {
|
setCurrentSpace(spaceIdForRoom, false);
|
||||||
setCurrentSpace("DM"_L1, false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const auto &parentSpaces = SpaceHierarchyCache::instance().parentSpaces(roomId);
|
|
||||||
if (parentSpaces.contains(m_currentSpaceId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
static auto config = NeoChatConfig::self();
|
|
||||||
if (config->allRoomsInHome()) {
|
|
||||||
setCurrentSpace({}, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (const auto &parent = m_connection->room(m_currentRoom->canonicalParent())) {
|
|
||||||
for (const auto &parentParent : SpaceHierarchyCache::instance().parentSpaces(parent->id())) {
|
|
||||||
if (SpaceHierarchyCache::instance().parentSpaces(parentParent).isEmpty()) {
|
|
||||||
setCurrentSpace(parentParent, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setCurrentSpace(parent->id(), false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const auto &space : parentSpaces) {
|
|
||||||
if (SpaceHierarchyCache::instance().parentSpaces(space).isEmpty()) {
|
|
||||||
setCurrentSpace(space, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setCurrentSpace({}, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomManager::clearCurrentRoom()
|
void RoomManager::clearCurrentRoom()
|
||||||
|
|||||||
@@ -233,7 +233,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Show a context menu for the given event.
|
* @brief Show a context menu for the given event.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText = {}, const QString &hoveredLink = {});
|
Q_INVOKABLE void
|
||||||
|
viewEventMenu(QObject *parent, const RoomEvent *event, NeoChatRoom *room, const QString &selectedText = {}, const QString &hoveredLink = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set a URL to be loaded as the initial room.
|
* @brief Set a URL to be loaded as the initial room.
|
||||||
@@ -306,7 +307,8 @@ Q_SIGNALS:
|
|||||||
/**
|
/**
|
||||||
* @brief Request to show a menu for the given event.
|
* @brief Request to show a menu for the given event.
|
||||||
*/
|
*/
|
||||||
void showDelegateMenu(const QString &eventId,
|
void showDelegateMenu(QObject *parent,
|
||||||
|
const QString &eventId,
|
||||||
const NeochatRoomMember *author,
|
const NeochatRoomMember *author,
|
||||||
MessageComponentType::Type messageComponentType,
|
MessageComponentType::Type messageComponentType,
|
||||||
const QString &plainText,
|
const QString &plainText,
|
||||||
@@ -373,6 +375,15 @@ private:
|
|||||||
|
|
||||||
void setCurrentRoom(const QString &roomId);
|
void setCurrentRoom(const QString &roomId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the most appropriate space for the currently selected room
|
||||||
|
*
|
||||||
|
* Should be used to figure out what space to switch to after a room change.
|
||||||
|
*
|
||||||
|
* @return The Space ID that the currently set room should be displayed as part of. (or "DM" for DM and "" for Home)
|
||||||
|
*/
|
||||||
|
QString findSpaceIdForCurrentRoom() const;
|
||||||
|
|
||||||
// Space ID, "DM", or empty string
|
// Space ID, "DM", or empty string
|
||||||
void setCurrentSpace(const QString &spaceId, bool setRoom = true);
|
void setCurrentSpace(const QString &spaceId, bool setRoom = true);
|
||||||
|
|
||||||
|
|||||||
@@ -286,6 +286,8 @@ QQC2.Control {
|
|||||||
quickFormatBar.selectionStart = selectionStart;
|
quickFormatBar.selectionStart = selectionStart;
|
||||||
quickFormatBar.selectionEnd = selectionEnd;
|
quickFormatBar.selectionEnd = selectionEnd;
|
||||||
quickFormatBar.open();
|
quickFormatBar.open();
|
||||||
|
} else if (quickFormatBar.visible) {
|
||||||
|
quickFormatBar.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ QQC2.Popup {
|
|||||||
padding: 2
|
padding: 2
|
||||||
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
|
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
|
||||||
width: Math.min(contentItem.categoryIconSize * 11 + 2 * padding, QQC2.ApplicationWindow.window.width)
|
width: Math.min(contentItem.categoryIconSize * 11 + 2 * padding, QQC2.ApplicationWindow.window?.width)
|
||||||
contentItem: EmojiPicker {
|
contentItem: EmojiPicker {
|
||||||
id: emojiPicker
|
id: emojiPicker
|
||||||
height: 400
|
height: 400
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ ColumnLayout {
|
|||||||
id: quickReactions
|
id: quickReactions
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
|
model: ["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"]
|
||||||
|
|
||||||
delegate: EmojiDelegate {
|
delegate: EmojiDelegate {
|
||||||
required property string modelData
|
required property string modelData
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ FormCard.FormCard {
|
|||||||
|
|
||||||
onToggled: NeoChatConfig.threads = checked
|
onToggled: NeoChatConfig.threads = checked
|
||||||
}
|
}
|
||||||
FormCard.FormCheckDelegate {
|
|
||||||
text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup")
|
|
||||||
checked: NeoChatConfig.secretBackup
|
|
||||||
|
|
||||||
onToggled: NeoChatConfig.secretBackup = checked
|
|
||||||
}
|
|
||||||
FormCard.FormCheckDelegate {
|
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")
|
text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs")
|
||||||
checked: NeoChatConfig.phone3PId
|
checked: NeoChatConfig.phone3PId
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ target_sources(LibNeoChat PRIVATE
|
|||||||
events/imagepackevent.cpp
|
events/imagepackevent.cpp
|
||||||
events/pollevent.cpp
|
events/pollevent.cpp
|
||||||
jobs/neochatgetcommonroomsjob.cpp
|
jobs/neochatgetcommonroomsjob.cpp
|
||||||
|
jobs/neochatreportroomjob.cpp
|
||||||
|
jobs/neochatreportuserjob.cpp
|
||||||
models/actionsmodel.cpp
|
models/actionsmodel.cpp
|
||||||
models/completionmodel.cpp
|
models/completionmodel.cpp
|
||||||
models/completionproxymodel.cpp
|
models/completionproxymodel.cpp
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
#include "chatbarcache.h"
|
#include "chatbarcache.h"
|
||||||
|
|
||||||
|
#include <QMimeData>
|
||||||
|
|
||||||
|
#include <KUrlMimeData>
|
||||||
|
|
||||||
#include <Quotient/roommember.h>
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
@@ -295,4 +299,17 @@ void ChatBarCache::clearCache()
|
|||||||
clearRelations();
|
clearRelations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatBarCache::drop(QList<QUrl> u, const QString &transferPortal)
|
||||||
|
{
|
||||||
|
QMimeData mimeData;
|
||||||
|
mimeData.setUrls(u);
|
||||||
|
if (!transferPortal.isEmpty()) {
|
||||||
|
mimeData.setData(u"application/vnd.portal.filetransfer"_s, transferPortal.toLatin1());
|
||||||
|
}
|
||||||
|
auto urls = KUrlMimeData::urlsFromMimeData(&mimeData);
|
||||||
|
if (urls.size() > 0) {
|
||||||
|
setAttachmentPath(urls[0].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_chatbarcache.cpp"
|
#include "moc_chatbarcache.cpp"
|
||||||
|
|||||||
@@ -198,6 +198,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE void postMessage();
|
Q_INVOKABLE void postMessage();
|
||||||
|
|
||||||
|
Q_INVOKABLE void drop(QList<QUrl> urls, const QString &transferPortal);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void textChanged();
|
void textChanged();
|
||||||
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ QString PowerLevel::nameForLevel(Level level)
|
|||||||
return i18n("Moderator");
|
return i18n("Moderator");
|
||||||
case PowerLevel::Admin:
|
case PowerLevel::Admin:
|
||||||
return i18n("Admin");
|
return i18n("Admin");
|
||||||
|
case PowerLevel::Owner:
|
||||||
|
return i18nc("The person that owns a room", "Owner");
|
||||||
|
case PowerLevel::Creator:
|
||||||
|
return i18nc("The person that created a room", "Creator");
|
||||||
case PowerLevel::Mute:
|
case PowerLevel::Mute:
|
||||||
return i18n("Mute");
|
return i18n("Mute");
|
||||||
case PowerLevel::Custom:
|
case PowerLevel::Custom:
|
||||||
@@ -30,6 +34,8 @@ int PowerLevel::valueForLevel(Level level)
|
|||||||
return 50;
|
return 50;
|
||||||
case PowerLevel::Admin:
|
case PowerLevel::Admin:
|
||||||
return 100;
|
return 100;
|
||||||
|
case PowerLevel::Owner:
|
||||||
|
return 150;
|
||||||
case PowerLevel::Mute:
|
case PowerLevel::Mute:
|
||||||
return -1;
|
return -1;
|
||||||
default:
|
default:
|
||||||
@@ -46,8 +52,12 @@ PowerLevel::Level PowerLevel::levelForValue(int value)
|
|||||||
return PowerLevel::Moderator;
|
return PowerLevel::Moderator;
|
||||||
case 100:
|
case 100:
|
||||||
return PowerLevel::Admin;
|
return PowerLevel::Admin;
|
||||||
|
case 150:
|
||||||
|
return PowerLevel::Owner;
|
||||||
case -1:
|
case -1:
|
||||||
return PowerLevel::Mute;
|
return PowerLevel::Mute;
|
||||||
|
case std::numeric_limits<int>::max():
|
||||||
|
return PowerLevel::Creator;
|
||||||
default:
|
default:
|
||||||
return PowerLevel::Custom;
|
return PowerLevel::Custom;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ public:
|
|||||||
enum Level {
|
enum Level {
|
||||||
Member, /**< A basic member. */
|
Member, /**< A basic member. */
|
||||||
Moderator, /**< A moderator with enhanced powers. */
|
Moderator, /**< A moderator with enhanced powers. */
|
||||||
Admin, /**< The highest power level in the room. */
|
Admin, /**< Power level 100. */
|
||||||
|
Owner, /**< Power level 150. */
|
||||||
Mute, /**< The level to remove posting privileges. */
|
Mute, /**< The level to remove posting privileges. */
|
||||||
NUMLevels,
|
NUMLevels,
|
||||||
Custom, /**< A non-standard value. Intentionally after NUMLevels so it doesn't appear in the model. */
|
Custom, /**< A non-standard value. Intentionally after NUMLevels so it doesn't appear in the model. */
|
||||||
|
Creator, /**< The user creating the (co-)creating the room. */
|
||||||
};
|
};
|
||||||
Q_ENUM(Level);
|
Q_ENUM(Level);
|
||||||
|
|
||||||
|
|||||||
@@ -389,9 +389,9 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
|||||||
return i18n("left the room");
|
return i18n("left the room");
|
||||||
}
|
}
|
||||||
if (const auto &reason = e.contentJson()["reason"_L1].toString().toHtmlEscaped(); !reason.isEmpty()) {
|
if (const auto &reason = e.contentJson()["reason"_L1].toString().toHtmlEscaped(); !reason.isEmpty()) {
|
||||||
return i18n("has put %1 out of the room: %2", subjectName, reason);
|
return i18n("has removed %1 from the room: %2", subjectName, reason);
|
||||||
}
|
}
|
||||||
return i18n("has put %1 out of the room", subjectName);
|
return i18n("has removed %1 from the room", subjectName);
|
||||||
case Membership::Ban:
|
case Membership::Ban:
|
||||||
if (e.senderId() != e.userId()) {
|
if (e.senderId() != e.userId()) {
|
||||||
if (e.reason().isEmpty()) {
|
if (e.reason().isEmpty()) {
|
||||||
@@ -697,6 +697,9 @@ QString EventHandler::subtitleText(const NeoChatRoom *room, const Quotient::Room
|
|||||||
qCWarning(EventHandling) << "subtitleText called with event set to nullptr.";
|
qCWarning(EventHandling) << "subtitleText called with event set to nullptr.";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
if (room->isDirectChat()) {
|
||||||
|
return plainBody(room, event, true);
|
||||||
|
}
|
||||||
return singleLineAuthorDisplayname(room, event) + (event->isStateEvent() ? u" "_s : u": "_s) + plainBody(room, event, true);
|
return singleLineAuthorDisplayname(room, event) + (event->isStateEvent() ? u" "_s : u": "_s) + plainBody(room, event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/libneochat/jobs/neochatreportroomjob.cpp
Normal file
14
src/libneochat/jobs/neochatreportroomjob.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "neochatreportroomjob.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
NeochatReportRoomJob::NeochatReportRoomJob(const QString &userId, const QString &reason)
|
||||||
|
: BaseJob(HttpVerb::Post, u"ReportRoomJob"_s, makePath(" /_matrix/client/v3/", userId, "/report"))
|
||||||
|
{
|
||||||
|
QJsonObject _dataJson;
|
||||||
|
addParam<IfNotEmpty>(_dataJson, "reason"_L1, reason);
|
||||||
|
setRequestData({_dataJson});
|
||||||
|
}
|
||||||
13
src/libneochat/jobs/neochatreportroomjob.h
Normal file
13
src/libneochat/jobs/neochatreportroomjob.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
|
||||||
|
// TODO: Remove once libQuotient updates to Matrix API v1.14
|
||||||
|
class NeochatReportRoomJob : public Quotient::BaseJob
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit NeochatReportRoomJob(const QString &roomId, const QString &reason);
|
||||||
|
};
|
||||||
14
src/libneochat/jobs/neochatreportuserjob.cpp
Normal file
14
src/libneochat/jobs/neochatreportuserjob.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "neochatreportuserjob.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
NeochatReportUserJob::NeochatReportUserJob(const QString &userId, const QString &reason)
|
||||||
|
: BaseJob(HttpVerb::Post, u"ReportUserJob"_s, makePath("/_matrix/client/v3/users/", userId, "/report"))
|
||||||
|
{
|
||||||
|
QJsonObject _dataJson;
|
||||||
|
addParam<IfNotEmpty>(_dataJson, "reason"_L1, reason);
|
||||||
|
setRequestData({_dataJson});
|
||||||
|
}
|
||||||
13
src/libneochat/jobs/neochatreportuserjob.h
Normal file
13
src/libneochat/jobs/neochatreportuserjob.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
|
||||||
|
// TODO: Remove once libQuotient updates to Matrix API v1.14
|
||||||
|
class NeochatReportUserJob : public Quotient::BaseJob
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit NeochatReportUserJob(const QString &userId, const QString &reason);
|
||||||
|
};
|
||||||
@@ -59,7 +59,7 @@ QList<ActionsModel::Action> actions{
|
|||||||
Action{
|
Action{
|
||||||
u"shrug"_s,
|
u"shrug"_s,
|
||||||
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
|
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
|
||||||
return u"¯\\\\_(ツ)_/¯ %1"_s.arg(message);
|
return u"¯\\\\\\_(ツ)\\_/¯ %1"_s.arg(message);
|
||||||
},
|
},
|
||||||
Quotient::RoomMessageEvent::MsgType::Text,
|
Quotient::RoomMessageEvent::MsgType::Text,
|
||||||
kli18n("<message>"),
|
kli18n("<message>"),
|
||||||
|
|||||||
@@ -118,8 +118,10 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
|||||||
connect(room, &Room::displaynameChanged, this, [this, room] {
|
connect(room, &Room::displaynameChanged, this, [this, room] {
|
||||||
refresh(room, {DisplayNameRole});
|
refresh(room, {DisplayNameRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::unreadStatsChanged, this, [this, room] {
|
connect(room, &Room::changed, this, [this, room](Room::Changes changes) {
|
||||||
refresh(room, {ContextNotificationCountRole, HasHighlightNotificationsRole, NotificationCountRole});
|
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
|
||||||
|
refresh(room, {ContextNotificationCountRole, HasHighlightNotificationsRole, NotificationCountRole});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
#include "jobs/neochatreportuserjob.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
#include <Quotient/csapi/profile.h>
|
#include <Quotient/csapi/profile.h>
|
||||||
#include <Quotient/csapi/registration.h>
|
#include <Quotient/csapi/registration.h>
|
||||||
#include <Quotient/csapi/versions.h>
|
#include <Quotient/csapi/versions.h>
|
||||||
|
#include <Quotient/e2ee/sssshandler.h>
|
||||||
#include <Quotient/jobs/downloadfilejob.h>
|
#include <Quotient/jobs/downloadfilejob.h>
|
||||||
#include <Quotient/qt_connection_util.h>
|
#include <Quotient/qt_connection_util.h>
|
||||||
#include <Quotient/room.h>
|
#include <Quotient/room.h>
|
||||||
@@ -75,30 +77,37 @@ void NeoChatConnection::connectSignals()
|
|||||||
Q_EMIT directChatInvitesChanged();
|
Q_EMIT directChatInvitesChanged();
|
||||||
for (const auto &chatId : additions) {
|
for (const auto &chatId : additions) {
|
||||||
if (const auto chat = room(chatId)) {
|
if (const auto chat = room(chatId)) {
|
||||||
connect(chat, &Room::unreadStatsChanged, this, [this]() {
|
connect(chat, &Room::changed, this, [this](Room::Changes changes) {
|
||||||
refreshBadgeNotificationCount();
|
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
|
||||||
Q_EMIT directChatNotificationsChanged();
|
refreshBadgeNotificationCount();
|
||||||
Q_EMIT directChatsHaveHighlightNotificationsChanged();
|
Q_EMIT directChatNotificationsChanged();
|
||||||
|
Q_EMIT directChatsHaveHighlightNotificationsChanged();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto &chatId : removals) {
|
for (const auto &chatId : removals) {
|
||||||
if (const auto chat = room(chatId)) {
|
if (const auto chat = room(chatId)) {
|
||||||
disconnect(chat, &Room::unreadStatsChanged, this, nullptr);
|
disconnect(chat, &Room::changed, this, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) {
|
connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) {
|
||||||
if (room->isDirectChat()) {
|
if (room->isDirectChat()) {
|
||||||
connect(room, &Room::unreadStatsChanged, this, [this]() {
|
connect(room, &Room::changed, this, [this](Room::Changes changes) {
|
||||||
Q_EMIT directChatNotificationsChanged();
|
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
|
||||||
Q_EMIT directChatsHaveHighlightNotificationsChanged();
|
Q_EMIT directChatNotificationsChanged();
|
||||||
|
Q_EMIT directChatsHaveHighlightNotificationsChanged();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
connect(room, &Room::unreadStatsChanged, this, [this]() {
|
Q_EMIT roomInvitesChanged();
|
||||||
refreshBadgeNotificationCount();
|
connect(room, &Room::changed, this, [this](Room::Changes changes) {
|
||||||
Q_EMIT homeNotificationsChanged();
|
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
|
||||||
Q_EMIT homeHaveHighlightNotificationsChanged();
|
refreshBadgeNotificationCount();
|
||||||
|
Q_EMIT homeNotificationsChanged();
|
||||||
|
Q_EMIT homeHaveHighlightNotificationsChanged();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
|
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
|
||||||
@@ -133,9 +142,9 @@ void NeoChatConnection::connectSignals()
|
|||||||
this,
|
this,
|
||||||
[this] {
|
[this] {
|
||||||
callApi<GetVersionsJob>(BackgroundRequest).onResult([this](const auto &job) {
|
callApi<GetVersionsJob>(BackgroundRequest).onResult([this](const auto &job) {
|
||||||
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
|
m_canCheckMutualRooms = job->unstableFeatures().value("uk.half-shot.msc2666.query_mutual_rooms"_L1, false);
|
||||||
Q_EMIT canCheckMutualRoomsChanged();
|
Q_EMIT canCheckMutualRoomsChanged();
|
||||||
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
|
m_canEraseData = job->unstableFeatures().value("org.matrix.msc4025"_L1, false) || job->versions().count("v1.10"_L1);
|
||||||
Q_EMIT canEraseDataChanged();
|
Q_EMIT canEraseDataChanged();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -159,6 +168,10 @@ void NeoChatConnection::refreshBadgeNotificationCount()
|
|||||||
for (const auto &r : allRooms()) {
|
for (const auto &r : allRooms()) {
|
||||||
if (const auto room = static_cast<NeoChatRoom *>(r)) {
|
if (const auto room = static_cast<NeoChatRoom *>(r)) {
|
||||||
count += room->contextAwareNotificationCount();
|
count += room->contextAwareNotificationCount();
|
||||||
|
|
||||||
|
if (room->joinState() == JoinState::Invite) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,15 +465,20 @@ bool NeoChatConnection::homeHaveHighlightNotifications() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatConnection::directChatInvites() const
|
qsizetype NeoChatConnection::directChatInvites() const
|
||||||
{
|
{
|
||||||
auto inviteRooms = rooms(JoinState::Invite);
|
const auto inviteRooms = rooms(JoinState::Invite);
|
||||||
for (const auto inviteRoom : inviteRooms) {
|
return std::ranges::count_if(inviteRooms, [](const auto room) {
|
||||||
if (inviteRoom->isDirectChat()) {
|
return room->isDirectChat();
|
||||||
return true;
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
qsizetype NeoChatConnection::roomInvites() const
|
||||||
|
{
|
||||||
|
const auto inviteRooms = rooms(JoinState::Invite);
|
||||||
|
return std::ranges::count_if(inviteRooms, [](const auto room) {
|
||||||
|
return !room->isDirectChat();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
|
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
|
||||||
@@ -564,4 +582,58 @@ bool NeoChatConnection::isVerifiedSession() const
|
|||||||
return isVerifiedDevice(userId(), deviceId());
|
return isVerifiedDevice(userId(), deviceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatConnection::unlockSSSS(const QString &secret)
|
||||||
|
{
|
||||||
|
auto handler = new SSSSHandler();
|
||||||
|
handler->setConnection(this);
|
||||||
|
connect(handler, &SSSSHandler::error, this, [secret, handler, this]() {
|
||||||
|
disconnect(handler, &SSSSHandler::error, this, nullptr);
|
||||||
|
if (!secret.isEmpty()) {
|
||||||
|
connect(handler, &SSSSHandler::error, this, [handler, this]() {
|
||||||
|
Q_EMIT keyBackupError();
|
||||||
|
delete handler;
|
||||||
|
});
|
||||||
|
handler->unlockSSSSWithPassphrase(secret);
|
||||||
|
} else {
|
||||||
|
Q_EMIT keyBackupError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(handler, &SSSSHandler::keyBackupUnlocked, this, [handler, this]() {
|
||||||
|
Q_EMIT keyBackupUnlocked();
|
||||||
|
connect(handler, &SSSSHandler::finished, handler, &SSSSHandler::deleteLater);
|
||||||
|
});
|
||||||
|
if (secret.isEmpty()) {
|
||||||
|
handler->unlockSSSSFromCrossSigning();
|
||||||
|
} else {
|
||||||
|
handler->unlockSSSSFromSecurityKey(secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatConnection::reportUser(const QString &userId, const QString &reason)
|
||||||
|
{
|
||||||
|
callApi<NeochatReportUserJob>(userId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeoChatConnection::supportsMatrixSpecVersion(const QString &version)
|
||||||
|
{
|
||||||
|
return supportedMatrixSpecVersions().contains(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NeoChatConnection::noteForUser(const QString &userId)
|
||||||
|
{
|
||||||
|
const auto object = accountDataJson(QStringLiteral("org.kde.neochat.user_note"));
|
||||||
|
return object[userId].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatConnection::setNoteForUser(const QString &userId, const QString ¬e)
|
||||||
|
{
|
||||||
|
auto object = accountDataJson(QStringLiteral("org.kde.neochat.user_note"));
|
||||||
|
if (note.isEmpty()) {
|
||||||
|
object.remove(userId);
|
||||||
|
} else {
|
||||||
|
object[userId] = note;
|
||||||
|
}
|
||||||
|
setAccountData(QStringLiteral("org.kde.neochat.user_note"), object);
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_neochatconnection.cpp"
|
#include "moc_neochatconnection.cpp"
|
||||||
|
|||||||
@@ -66,9 +66,14 @@ class NeoChatConnection : public Quotient::Connection
|
|||||||
Q_PROPERTY(bool homeHaveHighlightNotifications READ homeHaveHighlightNotifications NOTIFY homeHaveHighlightNotificationsChanged)
|
Q_PROPERTY(bool homeHaveHighlightNotifications READ homeHaveHighlightNotifications NOTIFY homeHaveHighlightNotificationsChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether there is at least one invite to a direct chat.
|
* @brief The number of invites to 1-on-1 direct chats.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
|
Q_PROPERTY(qsizetype directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of pending, normal room invites.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(qsizetype roomInvites READ roomInvites NOTIFY roomInvitesChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the server supports querying a user's mutual rooms.
|
* @brief Whether the server supports querying a user's mutual rooms.
|
||||||
@@ -200,7 +205,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
static void setKeywordPushRuleDefault(PushRuleAction::Action defaultAction);
|
static void setKeywordPushRuleDefault(PushRuleAction::Action defaultAction);
|
||||||
|
|
||||||
bool directChatInvites() const;
|
qsizetype directChatInvites() const;
|
||||||
|
qsizetype roomInvites() const;
|
||||||
|
|
||||||
// note: this is intentionally a copied QString because
|
// note: this is intentionally a copied QString because
|
||||||
// the reference could be destroyed before the task is finished
|
// the reference could be destroyed before the task is finished
|
||||||
@@ -216,6 +222,28 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool isVerifiedSession() const;
|
bool isVerifiedSession() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void unlockSSSS(const QString &secret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Report a user.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void reportUser(const QString &userId, const QString &reason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if this connection supports the given spec version (e.g. "v1.11").
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE bool supportsMatrixSpecVersion(const QString &version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The private note for this user, if set.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE QString noteForUser(const QString &userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the private note for this user.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void setNoteForUser(const QString &userId, const QString ¬e);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void globalUrlPreviewEnabledChanged();
|
void globalUrlPreviewEnabledChanged();
|
||||||
void labelChanged();
|
void labelChanged();
|
||||||
@@ -225,6 +253,7 @@ Q_SIGNALS:
|
|||||||
void homeNotificationsChanged();
|
void homeNotificationsChanged();
|
||||||
void homeHaveHighlightNotificationsChanged();
|
void homeHaveHighlightNotificationsChanged();
|
||||||
void directChatInvitesChanged();
|
void directChatInvitesChanged();
|
||||||
|
void roomInvitesChanged();
|
||||||
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
||||||
void userConsentRequired(QUrl url);
|
void userConsentRequired(QUrl url);
|
||||||
void badgeNotificationCountChanged(int count);
|
void badgeNotificationCountChanged(int count);
|
||||||
@@ -252,6 +281,9 @@ Q_SIGNALS:
|
|||||||
*/
|
*/
|
||||||
void ownSessionVerified();
|
void ownSessionVerified();
|
||||||
|
|
||||||
|
void keyBackupUnlocked();
|
||||||
|
void keyBackupError();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool m_globalUrlPreviewDefault;
|
static bool m_globalUrlPreviewDefault;
|
||||||
static PushRuleAction::Action m_defaultAction;
|
static PushRuleAction::Action m_defaultAction;
|
||||||
|
|||||||
@@ -50,12 +50,12 @@
|
|||||||
#include "roomlastmessageprovider.h"
|
#include "roomlastmessageprovider.h"
|
||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
#include "urlhelper.h"
|
#include "urlhelper.h"
|
||||||
|
#include "jobs/neochatreportroomjob.h"
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <KIO/Job>
|
#include <KIO/Job>
|
||||||
#include <KIO/JobTracker>
|
#include <KIO/JobTracker>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <KJobTrackerInterface>
|
#include <KJobTrackerInterface>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
@@ -169,6 +169,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
const auto neochatconnection = static_cast<NeoChatConnection *>(connection);
|
const auto neochatconnection = static_cast<NeoChatConnection *>(connection);
|
||||||
Q_ASSERT(neochatconnection);
|
Q_ASSERT(neochatconnection);
|
||||||
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
|
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
|
||||||
|
connect(this, &Room::fullyReadMarkerMoved, this, &NeoChatRoom::invalidateLastUnreadHighlightId);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::visible() const
|
bool NeoChatRoom::visible() const
|
||||||
@@ -346,6 +347,10 @@ void NeoChatRoom::forget()
|
|||||||
|
|
||||||
void NeoChatRoom::sendTypingNotification(bool isTyping)
|
void NeoChatRoom::sendTypingNotification(bool isTyping)
|
||||||
{
|
{
|
||||||
|
// During the chatbar setup sequence, this may get called while we're still initializing
|
||||||
|
if (localMember().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
connection()->callApi<SetTypingJob>(BackgroundRequest, localMember().id(), id(), isTyping, 10000);
|
connection()->callApi<SetTypingJob>(BackgroundRequest, localMember().id(), id(), isTyping, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1181,7 +1186,10 @@ void NeoChatRoom::loadPinnedMessage()
|
|||||||
connection()->callApi<GetOneRoomEventJob>(id(), mostRecentEventId).then([this](const auto &job) {
|
connection()->callApi<GetOneRoomEventJob>(id(), mostRecentEventId).then([this](const auto &job) {
|
||||||
auto event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
auto event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||||
if (auto encEv = eventCast<EncryptedEvent>(event.get())) {
|
if (auto encEv = eventCast<EncryptedEvent>(event.get())) {
|
||||||
event = decryptMessage(*encEv);
|
auto decryptedMessage = decryptMessage(*encEv);
|
||||||
|
if (decryptedMessage) {
|
||||||
|
event = std::move(decryptedMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_pinnedMessage = EventHandler::richBody(this, event.get());
|
m_pinnedMessage = EventHandler::richBody(this, event.get());
|
||||||
Q_EMIT pinnedMessageChanged();
|
Q_EMIT pinnedMessageChanged();
|
||||||
@@ -1649,6 +1657,12 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
event_ptr_tt<RoomEvent> event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
event_ptr_tt<RoomEvent> event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||||
|
if (auto encEv = eventCast<EncryptedEvent>(event.get())) {
|
||||||
|
auto decryptedEvent = decryptMessage(*encEv);
|
||||||
|
if (decryptedEvent) {
|
||||||
|
event = std::move(decryptedEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
m_extraEvents.push_back(std::move(event));
|
m_extraEvents.push_back(std::move(event));
|
||||||
Q_EMIT extraEventLoaded(eventId);
|
Q_EMIT extraEventLoaded(eventId);
|
||||||
},
|
},
|
||||||
@@ -1686,6 +1700,11 @@ std::pair<const Quotient::RoomEvent *, bool> NeoChatRoom::getEvent(const QString
|
|||||||
return std::make_pair(extraIt != m_extraEvents.end() ? extraIt->get() : nullptr, false);
|
return std::make_pair(extraIt != m_extraEvents.end() ? extraIt->get() : nullptr, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RoomEvent *NeoChatRoom::findEvent(const QString &eventId) const
|
||||||
|
{
|
||||||
|
return getEvent(eventId).first;
|
||||||
|
}
|
||||||
|
|
||||||
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const
|
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const
|
||||||
{
|
{
|
||||||
#if Quotient_VERSION_MINOR > 9
|
#if Quotient_VERSION_MINOR > 9
|
||||||
@@ -1834,4 +1853,57 @@ QString NeoChatRoom::pinnedMessage() const
|
|||||||
return m_pinnedMessage;
|
return m_pinnedMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::report(const QString &reason)
|
||||||
|
{
|
||||||
|
connection()->callApi<NeochatReportRoomJob>(id(), reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NeoChatRoom::findNextUnreadHighlightId()
|
||||||
|
{
|
||||||
|
const QString startEventId = !m_lastUnreadHighlightId.isEmpty() ? m_lastUnreadHighlightId : lastFullyReadEventId();
|
||||||
|
const auto startIt = findInTimeline(startEventId);
|
||||||
|
if (startIt == historyEdge()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = startIt.base(); it != messageEvents().cend(); ++it) {
|
||||||
|
const RoomEvent *ev = it->event();
|
||||||
|
if (highlights.contains(ev)) {
|
||||||
|
m_lastUnreadHighlightId = ev->id();
|
||||||
|
Q_EMIT highlightCycleStartedChanged();
|
||||||
|
return m_lastUnreadHighlightId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_lastUnreadHighlightId.isEmpty()) {
|
||||||
|
m_lastUnreadHighlightId.clear();
|
||||||
|
Q_EMIT highlightCycleStartedChanged();
|
||||||
|
return findNextUnreadHighlightId();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeoChatRoom::highlightCycleStarted() const
|
||||||
|
{
|
||||||
|
return !m_lastUnreadHighlightId.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId)
|
||||||
|
{
|
||||||
|
Q_UNUSED(fromEventId);
|
||||||
|
|
||||||
|
if (m_lastUnreadHighlightId.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto lastIt = findInTimeline(m_lastUnreadHighlightId);
|
||||||
|
const auto newReadIt = findInTimeline(toEventId);
|
||||||
|
|
||||||
|
// opposite comparision because both are reverse iterators :p
|
||||||
|
if (newReadIt <= lastIt) {
|
||||||
|
m_lastUnreadHighlightId.clear();
|
||||||
|
Q_EMIT highlightCycleStartedChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_neochatroom.cpp"
|
#include "moc_neochatroom.cpp"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user