Compare commits
49 commits
group-voic
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15322555e8 | ||
|
|
b083dc801c | ||
|
|
95532442ca | ||
|
|
b1c387e03c | ||
|
|
5894e32482 | ||
|
|
58e23302d0 | ||
|
|
51da48c706 | ||
|
|
014d70fd64 | ||
|
|
f91427e653 | ||
|
|
e3bc058845 | ||
|
|
3184ab464c | ||
|
|
abb2325a99 | ||
|
|
ee62c9990d | ||
|
|
b5ce330c82 | ||
|
|
89e06f32dd | ||
|
|
597aa36f28 | ||
|
|
ee5fbe2927 | ||
|
|
c17734c7a0 | ||
|
|
3a707a5ee5 | ||
|
|
2ece2aee9f | ||
|
|
e13c7449a4 | ||
|
|
16c8d95208 | ||
|
|
3d9e14e001 | ||
|
|
d9aa04778a | ||
|
|
9650c5f4be | ||
|
|
4effdc6955 | ||
|
|
5b065f353c | ||
|
|
93ce60d6f1 | ||
|
|
1bd2970c4d | ||
|
|
c33f7fde6d | ||
|
|
451e88fe72 | ||
|
|
b0d09926a1 | ||
|
|
4a85031516 | ||
|
|
2769642d3c | ||
|
|
53cd31d181 | ||
|
|
5b025fa2b0 | ||
|
|
af2ca72030 | ||
|
|
5d05753eeb | ||
|
|
896e44d507 | ||
|
|
ba9fab78d5 | ||
|
|
c7f191519c | ||
|
|
f1d88ea0a3 | ||
|
|
ffaa12cc19 | ||
|
|
398cef5f8f | ||
|
|
f59f77a21e | ||
|
|
2bc2dfb64c | ||
|
|
9c017ba1e2 | ||
|
|
a13ea11e57 | ||
|
|
a7bc00d9a5 |
54 changed files with 640 additions and 322 deletions
|
|
@ -24,6 +24,7 @@ cmake -GNinja -S. -Bbuild \
|
|||
-DCMAKE_INSTALL_PREFIX="nheko.temp" \
|
||||
-DHUNTER_ROOT="../.hunter" \
|
||||
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \
|
||||
-DKDSingleApplication_STATIC=ON -DKDSingleApplication_EXAMPLES=OFF \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \
|
||||
-DQt6_DIR=${QT_BASEPATH}/lib/cmake \
|
||||
-DCI_BUILD=ON
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ echo %DATE%
|
|||
|
||||
|
||||
call "C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Auxiliary/Build/vcvarsall.bat" x64
|
||||
set CMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
cmake -G "Visual Studio 17 2022" -A x64 -S. -Bbuild -DHUNTER_ROOT="C:\hunter" -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON -DUSE_BUNDLED_KDSINGLEAPPLICATION=ON -DKDSingleApplication_STATIC=ON -DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release
|
||||
cmake --build build --config Release -j %NUMBER_OF_PROCESSORS%
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ KeepEmptyLinesAtTheStartOfBlocks: false
|
|||
PointerAlignment: Right
|
||||
Cpp11BracedListStyle: true
|
||||
PenaltyReturnTypeOnItsOwnLine: 0
|
||||
StatementAttributeLikeMacros:
|
||||
- emit
|
||||
---
|
||||
BasedOnStyle: WebKit
|
||||
Language: ObjC
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ pages:
|
|||
- export LATEST_WINDOWS_NIGHTLY=$(curl "https://nheko.im/api/v4/projects/2/packages?package_name=windows-nightly&order_by=version&sort=desc" | jq -r '.[0].version')
|
||||
#- export LATEST_WINDOWS=$(curl "https://nheko.im/api/v4/projects/2/packages?package_name=windows&order_by=version&sort=desc" | jq -r '.[0].version')
|
||||
# hardcoded to avoid fuzzy matching
|
||||
- export LATEST_WINDOWS='0.12.0.35798'
|
||||
- export LATEST_WINDOWS='0.12.0.38759'
|
||||
- sed "s/0.12.1.0/${LATEST_WINDOWS_NIGHTLY}/g" -i resources/NhekoNightly.appinstaller
|
||||
- sed "s/0.12.1.0/${LATEST_WINDOWS}/g" -i resources/Nheko.appinstaller
|
||||
- mkdir public
|
||||
|
|
@ -450,8 +450,8 @@ linting:
|
|||
before_script:
|
||||
- apk update && apk add make git python3 py3-pip qt6-qtdeclarative-dev
|
||||
# clang18 seems to mess with the emit keyword when using the `->` operator
|
||||
- apk add clang17-extra-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main
|
||||
- export PATH="$PATH:/usr/lib/llvm17/bin/:/root/.local/bin"
|
||||
- apk add clang-extra-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main
|
||||
- export PATH="$PATH:/usr/lib/llvm/bin/:/root/.local/bin"
|
||||
- pip3 install --break-system-packages --user reuse
|
||||
script:
|
||||
- make lint
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "macos deployment target")
|
|||
option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
|
||||
include("cmake/HunterGate.cmake")
|
||||
HunterGate(
|
||||
URL "https://github.com/cpp-pm/hunter/archive/v0.26.1.tar.gz"
|
||||
SHA1 "e41ac7a18c49b35ebac99ff2b5244317b2638a65"
|
||||
URL "https://github.com/cpp-pm/hunter/archive/v0.26.6.tar.gz"
|
||||
SHA1 "e70c29f878f5d5f5cdf1b9ccd628fb872e8624a8"
|
||||
LOCAL
|
||||
)
|
||||
|
||||
|
|
@ -246,7 +246,11 @@ endif()
|
|||
#
|
||||
# Discover Qt dependencies.
|
||||
#
|
||||
|
||||
find_package(Qt6 6.5 COMPONENTS Core Widgets Gui LinguistTools Svg Multimedia Qml QuickControls2 REQUIRED)
|
||||
if (Qt6Qml_VERSION VERSION_GREATER_EQUAL "6.10.0")
|
||||
find_package(Qt6 REQUIRED COMPONENTS GuiPrivate QmlPrivate)
|
||||
endif()
|
||||
find_package(Qt6DBus)
|
||||
|
||||
if(USE_BUNDLED_QTKEYCHAIN)
|
||||
|
|
@ -295,7 +299,8 @@ if(NOT MSVC)
|
|||
-fsized-deallocation \
|
||||
-fdiagnostics-color=always \
|
||||
-Wunreachable-code \
|
||||
-Wno-attributes"
|
||||
-Wno-attributes \
|
||||
-Wno-error=unused-parameter"
|
||||
)
|
||||
if(NOT CMAKE_COMPILER_IS_GNUCXX)
|
||||
# -Wshadow is buggy and broken in GCC, so do not enable it.
|
||||
|
|
@ -539,7 +544,9 @@ if(USE_BUNDLED_OLM)
|
|||
Olm
|
||||
GIT_REPOSITORY https://gitlab.matrix.org/matrix-org/olm.git
|
||||
GIT_TAG 3.2.16
|
||||
PATCH_COMMAND git apply ${CMAKE_CURRENT_SOURCE_DIR}/third_party/olm-patches/0001-fix-list-const-ptr.patch
|
||||
PATCH_COMMAND git apply
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party/olm-patches/0001-fix-list-const-ptr.patch
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party/olm-patches/0002-fix-cmake-cmp0148.patch
|
||||
UPDATE_DISCONNECTED 1
|
||||
)
|
||||
set(OLM_TESTS OFF CACHE INTERNAL "")
|
||||
|
|
@ -562,16 +569,12 @@ if(USE_BUNDLED_CMARK)
|
|||
FetchContent_Declare(
|
||||
cmark
|
||||
GIT_REPOSITORY https://github.com/commonmark/cmark.git
|
||||
GIT_TAG 0.30.2
|
||||
CMAKE_ARGS "CMARK_STATIC=ON CMARK_SHARED=OFF CMARK_TESTS=OFF CMARK_TESTS=OFF"
|
||||
GIT_TAG 0.31.1
|
||||
CMAKE_ARGS "BUILD_TESTING=OFF"
|
||||
)
|
||||
FetchContent_MakeAvailable(cmark)
|
||||
if (NOT TARGET cmark::cmark)
|
||||
if(MSVC)
|
||||
add_library(cmark::cmark ALIAS cmark)
|
||||
else()
|
||||
add_library(cmark::cmark ALIAS cmark_static)
|
||||
endif()
|
||||
add_library(cmark::cmark ALIAS cmark)
|
||||
endif()
|
||||
else()
|
||||
find_package(cmark REQUIRED 0.29.0)
|
||||
|
|
@ -619,7 +622,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
|||
FetchContent_Declare(
|
||||
MatrixClient
|
||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||
GIT_TAG v0.10.1
|
||||
GIT_TAG 873911e352a0845dfb178f77b1ddea796a5d3455
|
||||
)
|
||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||
|
|
@ -713,7 +716,9 @@ set_target_properties(nheko
|
|||
#
|
||||
# Add qml files
|
||||
#
|
||||
|
||||
if(QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW)
|
||||
endif()
|
||||
set(QML_SOURCES
|
||||
resources/qml/Root.qml
|
||||
resources/qml/ChatPage.qml
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ sudo pacman -S qt6-base \
|
|||
gcc \
|
||||
fontconfig \
|
||||
lmdb \
|
||||
lmdbxx \
|
||||
cmark \
|
||||
qtkeychain-qt6
|
||||
```
|
||||
|
|
|
|||
|
|
@ -70,10 +70,12 @@ modules:
|
|||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMARK_TESTS=OFF
|
||||
- -DBUILD_TESTING=OFF
|
||||
- -DBUILD_SHARED_LIBS=OFF
|
||||
sources:
|
||||
- sha256: bbcb8f8c03b5af33fcfcf11a74e9499f20a9043200b8552f78a6e8ba76e04d11
|
||||
- sha256: 3da93db5469c30588cfeb283d9d62edfc6ded9eb0edc10a4f5bbfb7d722ea802
|
||||
type: archive
|
||||
url: https://github.com/commonmark/cmark/archive/0.31.0.tar.gz
|
||||
url: https://github.com/commonmark/cmark/archive/0.31.1.tar.gz
|
||||
- name: fmt
|
||||
buildsystem: cmake-ninja
|
||||
config-opts:
|
||||
|
|
@ -211,8 +213,8 @@ modules:
|
|||
- -DBUILD_SHARED_LIBS=OFF
|
||||
buildsystem: cmake-ninja
|
||||
sources:
|
||||
- commit: 15b43844f4ec27faa5f2ec92c4ded313206763aa
|
||||
tag: v0.10.1
|
||||
- commit: 873911e352a0845dfb178f77b1ddea796a5d3455
|
||||
#tag: v0.10.1
|
||||
type: git
|
||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||
- name: nheko
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
== NAME
|
||||
|
||||
nheko - Desktop client for Matrix using Qt and C++17
|
||||
nheko - Desktop client for Matrix using Qt and C++20
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
|
|
|
|||
1
resources/icons/ui/alert.svg
Normal file
1
resources/icons/ui/alert.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg width="32" height="32" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.996a7.49 7.49 0 0 1 7.496 7.25l.004.25v4.097l1.38 3.156a1.25 1.25 0 0 1-1.145 1.75L15 18.502a3 3 0 0 1-5.995.177L9 18.499H4.275a1.251 1.251 0 0 1-1.147-1.747L4.5 13.594V9.496c0-4.155 3.352-7.5 7.5-7.5ZM13.5 18.5l-3 .002a1.5 1.5 0 0 0 2.993.145l.006-.147ZM12 3.496c-3.32 0-6 2.674-6 6v4.41L4.656 17h14.697L18 13.907V9.509l-.004-.225A5.988 5.988 0 0 0 12 3.496Z" fill="#212121"/></svg>
|
||||
|
After Width: | Height: | Size: 494 B |
|
|
@ -17,7 +17,7 @@
|
|||
<message>
|
||||
<location line="+73"/>
|
||||
<source>You are screen sharing</source>
|
||||
<translation>Vous êtes en train de partager votre écran.</translation>
|
||||
<translation>Vous êtes en train de partager votre écran</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+17"/>
|
||||
|
|
@ -312,7 +312,7 @@
|
|||
<message>
|
||||
<location line="+32"/>
|
||||
<source>Kicked user: %1</source>
|
||||
<translation>L'utilisateur %1 a été expulsé.</translation>
|
||||
<translation>L'utilisateur %1 a été expulsé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+26"/>
|
||||
|
|
@ -322,7 +322,7 @@
|
|||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Banned user: %1</source>
|
||||
<translation>L'utilisateur %1 a été banni.</translation>
|
||||
<translation>L'utilisateur %1 a été banni</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+8"/>
|
||||
|
|
@ -625,32 +625,32 @@ Eventuellement, vous pouvez fournir une explication de votre demande aux autres
|
|||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Kick a user from the current room. Reason is optional. If user is left out, will try to kick the sender you are replying to.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Expulser un utilisateur de la salle actuelle. La raison est optionnelle. Si l'utilisateur est exclu, une tentative d'expulsion de l'utilisateur auquel vous êtes en train de répondre sera effectuée.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Ban a user from the current room. Reason is optional. If user is left out, will try to ban the sender you are replying to.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Bannir un utilisateur de la salle actuelle. La raison est optionnelle. Si l'utilisateur est exclu, une tentative de bannissement de l'utilisateur auquel vous êtes en train de répondre sera effectuée.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Unban a user in the current room. Reason is optional. If user is left out, will try to unban the sender you are replying to.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Annuler le banissement d'un utilisateur dans le salon actuel. La raison est optionnelle. Si l'utilisateur est exclu, une tentative d'annulation du bannissement de l'utilisateur auquel vous êtes en train de répondre sera effectuée.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Redact an event by event id or that you are replying to or all locally cached messages of a user.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Rédiger un événement grâce à un identifiant événement, ou celui auquel vous êtes en train de répondre, ou tous les messages de l'utilisateur mis en cache localement.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+56"/>
|
||||
<source>Block all invites from a user, a server, to a specific room or set the default.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Bloquer toutes les invitations en provenance d'un utilisateur ou d'un serveur pour un salon spécifique, ou bien définir le comportement par défaut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Allow all invites from a user, a server, to a specific room or set the default.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Autoriser toutes les invitations en provenance d'un utilisateur ou d'un serveur pour un salon spécifique, ou bien définir le comportement par défaut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-56"/>
|
||||
|
|
@ -750,12 +750,12 @@ Eventuellement, vous pouvez fournir une explication de votre demande aux autres
|
|||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Send a message with a glitch effect.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Envoyer un message avec un effet de déformation.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Send a message that gradually glitches.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Envoyer un message qui se déforme progressivement.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
|
|
@ -2143,7 +2143,7 @@ Exemple : https://serveur.domaine.extension:8787</translation>
|
|||
<location line="+12"/>
|
||||
<source>%1 replied with a spoiler.</source>
|
||||
<comment>Format a reply in a notification. %1 is the sender.</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>%1 a répondu avec un spoiler.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+10"/>
|
||||
|
|
@ -2250,7 +2250,7 @@ Exemple : https://serveur.domaine.extension:8787</translation>
|
|||
<message>
|
||||
<location line="-238"/>
|
||||
<source>User (%1)</source>
|
||||
<translation>Utilisateur (%)</translation>
|
||||
<translation>Utilisateur (%1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
|
|
@ -3706,7 +3706,7 @@ Si vous choisissez de vérifier, vous aurez besoin de l'autre appareil. Si
|
|||
<message>
|
||||
<location filename="../qml/components/SpaceMenu.qml" line="+16"/>
|
||||
<source>Add or remove from community...</source>
|
||||
<translation type="unfinished">Ajouter ou retirer de la communauté...</translation>
|
||||
<translation>Ajouter ou retirer de la communauté...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
|
@ -4307,9 +4307,9 @@ Raison : %4</translation>
|
|||
<message numerus="yes">
|
||||
<location line="+115"/>
|
||||
<source>%n hour(s) later</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
<translation>
|
||||
<numerusform>%n heure plus tard</numerusform>
|
||||
<numerusform>%n heures plus tard</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
|
@ -5495,7 +5495,7 @@ Cette fonctionnalité prendra effet au prochain redémarrage de l'applicati
|
|||
<message>
|
||||
<location line="+13"/>
|
||||
<source>Repeat File Password</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Répéter le mot de passe du fichier</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
|
|
@ -5686,7 +5686,7 @@ Cette fonctionnalité prendra effet au prochain redémarrage de l'applicati
|
|||
<message>
|
||||
<location filename="../../src/notifications/ManagerMac.cpp" line="-12"/>
|
||||
<source>Message contains spoiler.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Le message contient un spoiler.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
|
@ -5755,13 +5755,13 @@ Cette fonctionnalité prendra effet au prochain redémarrage de l'applicati
|
|||
<location line="+6"/>
|
||||
<location line="+26"/>
|
||||
<source>You sent a spoiler.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Vous avez envoyé un spoiler.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-23"/>
|
||||
<location line="+26"/>
|
||||
<source>%1 sent a spoiler.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>%1 a envoyé un spoiler.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-20"/>
|
||||
|
|
@ -5788,7 +5788,7 @@ Cette fonctionnalité prendra effet au prochain redémarrage de l'applicati
|
|||
<message>
|
||||
<location line="+23"/>
|
||||
<source>* %1 spoils something.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>* %1 a spoilé quelque chose.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+8"/>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ Popup {
|
|||
eventId: mid
|
||||
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
|
||||
maxWidth: parent.width
|
||||
limitHeight: true
|
||||
}
|
||||
MatrixTextField {
|
||||
id: roomTextInput
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ Rectangle {
|
|||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
visible: room ? room.permissions.canSend(room.isEncrypted ? MtxEvent.Encrypted : MtxEvent.TextMessage) : false
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
|
@ -170,15 +170,13 @@ Rectangle {
|
|||
} else if (event.matches(StandardKey.SelectAll) && popup.opened) {
|
||||
completer.completerName = "";
|
||||
popup.close();
|
||||
} else if (event.matches(StandardKey.InsertLineSeparator)) {
|
||||
if (popup.opened)
|
||||
popup.close();
|
||||
if (Settings.invertEnterKey) {
|
||||
room.input.send();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
if (popup.opened) {
|
||||
} else if (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) {
|
||||
// Handling popup takes priority over newline and sending message.
|
||||
if (popup.opened &&
|
||||
(event.modifiers == Qt.NoModifier
|
||||
|| event.modifiers == Qt.ShiftModifier
|
||||
|| event.modifiers == Qt.ControlModifier)
|
||||
) {
|
||||
var currentCompletion = completer.currentCompletion();
|
||||
let userid = completer.currentUserid();
|
||||
|
||||
|
|
@ -191,14 +189,26 @@ Rectangle {
|
|||
console.log(userid);
|
||||
room.input.addMention(userid, currentCompletion);
|
||||
}
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
if (!Settings.invertEnterKey) {
|
||||
// Send message Enter key combination event.
|
||||
else if (Settings.sendMessageKey == 0 && event.modifiers == Qt.NoModifier
|
||||
|| Settings.sendMessageKey == 1 && event.modifiers == Qt.ShiftModifier
|
||||
|| Settings.sendMessageKey == 2 && event.modifiers == Qt.ControlModifier
|
||||
) {
|
||||
room.input.send();
|
||||
event.accepted = true;
|
||||
}
|
||||
// Add newline Enter key combination event.
|
||||
else if (Settings.sendMessageKey == 0 && event.modifiers == Qt.ShiftModifier
|
||||
|| Settings.sendMessageKey == 1 && event.modifiers == Qt.NoModifier
|
||||
|| Settings.sendMessageKey == 2 && event.modifiers == Qt.ShiftModifier
|
||||
) {
|
||||
messageInput.insert(messageInput.cursorPosition, "\n");
|
||||
event.accepted = true;
|
||||
}
|
||||
// Any other Enter key combo is ignored here.
|
||||
} else if (event.key == Qt.Key_Tab && (event.modifiers == Qt.NoModifier || event.modifiers == Qt.ShiftModifier)) {
|
||||
event.accepted = true;
|
||||
if (popup.opened) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Item {
|
|||
property int availableWidth: width
|
||||
property int padding: Nheko.paddingMedium
|
||||
property string searchString: ""
|
||||
property bool filterByNotifications: false
|
||||
property Room roommodel: room
|
||||
|
||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||
|
|
@ -60,7 +61,7 @@ Item {
|
|||
boundsBehavior: Flickable.StopAtBounds
|
||||
displayMarginBeginning: height / 4
|
||||
displayMarginEnd: height / 4
|
||||
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
|
||||
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent || filteredTimeline.filterByNotifications) ? filteredTimeline : room
|
||||
//pixelAligned: true
|
||||
spacing: 2
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
|
|
@ -145,6 +146,7 @@ Item {
|
|||
id: filteredTimeline
|
||||
|
||||
filterByContent: chatRoot.searchString
|
||||
filterByNotifications: chatRoot.filterByNotifications
|
||||
filterByThread: room ? room.thread : ""
|
||||
source: room
|
||||
}
|
||||
|
|
@ -554,6 +556,8 @@ Item {
|
|||
Component {
|
||||
MenuItem {
|
||||
text: qsTr("&Mark as read")
|
||||
|
||||
onTriggered: room.markEventAsRead(messageContextMenuC.eventId)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
|
|
|
|||
|
|
@ -210,9 +210,10 @@ TimelineEvent {
|
|||
|
||||
AbstractButton {
|
||||
id: replyRow
|
||||
visible: wrapper.reply
|
||||
visible: wrapper.replyTo
|
||||
|
||||
leftPadding: Nheko.paddingSmall + 4
|
||||
|
||||
height: replyLine.height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
|
|
@ -225,19 +226,7 @@ TimelineEvent {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
contentItem: Row {
|
||||
id: replyRowLay
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
Rectangle {
|
||||
id: replyLine
|
||||
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
|
||||
color: replyRow.userColor
|
||||
width: 4
|
||||
}
|
||||
|
||||
Column {
|
||||
contentItem: Column {
|
||||
spacing: 0
|
||||
|
||||
id: replyCol
|
||||
|
|
@ -247,7 +236,7 @@ TimelineEvent {
|
|||
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
text: wrapper.reply?.userName ?? ''
|
||||
text: wrapper.reply?.userName ?? 'missing name'
|
||||
color: replyRow.userColor
|
||||
textFormat: Text.RichText
|
||||
width: wrapper.maxWidth
|
||||
|
|
@ -259,12 +248,20 @@ TimelineEvent {
|
|||
replyUserButton,
|
||||
wrapper.reply,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
//width: replyRow.implicitContentWidth
|
||||
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
|
||||
id: replyLine
|
||||
color: replyRow.userColor
|
||||
width: 4
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
|
|
|
|||
|
|
@ -192,9 +192,9 @@ TimelineEvent {
|
|||
|
||||
AbstractButton {
|
||||
id: replyRow
|
||||
visible: wrapper.reply
|
||||
visible: wrapper.replyTo
|
||||
|
||||
height: replyLine.height
|
||||
leftPadding: Nheko.paddingSmall + 4
|
||||
|
||||
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
|
||||
|
||||
|
|
@ -205,19 +205,7 @@ TimelineEvent {
|
|||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
contentItem: Row {
|
||||
id: replyRowLay
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
Rectangle {
|
||||
id: replyLine
|
||||
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
|
||||
color: replyRow.userColor
|
||||
width: 4
|
||||
}
|
||||
|
||||
Column {
|
||||
contentItem: Column {
|
||||
spacing: 0
|
||||
|
||||
id: replyCol
|
||||
|
|
@ -227,7 +215,7 @@ TimelineEvent {
|
|||
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
text: wrapper.reply?.userName ?? ''
|
||||
text: wrapper.reply?.userName ?? 'missing name'
|
||||
color: replyRow.userColor
|
||||
textFormat: Text.RichText
|
||||
width: wrapper.maxWidth
|
||||
|
|
@ -239,12 +227,20 @@ TimelineEvent {
|
|||
replyUserButton,
|
||||
wrapper.reply,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
//width: replyRow.implicitContentWidth
|
||||
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
|
||||
id: replyLine
|
||||
color: replyRow.userColor
|
||||
width: 4
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ Column {
|
|||
sourceSize.height: height
|
||||
|
||||
permissions: room ? room.permissions : null
|
||||
visible: isAdmin || isModerator
|
||||
visible: isAdmin || isModerator // implicitly includes creators as well
|
||||
}
|
||||
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ Item {
|
|||
Layout.fillWidth: true
|
||||
implicitHeight: msgView.height - typingIndicator.height
|
||||
searchString: topBar.searchString
|
||||
filterByNotifications: topBar.filterNotifications
|
||||
}
|
||||
Loader {
|
||||
source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? (Qt.platform.os != "windows" ? "voip/VideoCall.qml" : "voip/VideoCallD3D11.qml") : ""
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Pane {
|
|||
property bool searchHasFocus: searchField.focus && searchField.enabled
|
||||
property string searchString: ""
|
||||
property bool showBackButton: false
|
||||
property bool filterNotifications: false
|
||||
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -129,13 +130,30 @@ Pane {
|
|||
selectByMouse: false
|
||||
text: roomTopic
|
||||
}
|
||||
ImageButton {
|
||||
id: notificationsButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.column: 3
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.row: 1
|
||||
Layout.rowSpan: 2
|
||||
ToolTip.text: qsTr("Show only notifications")
|
||||
ToolTip.visible: hovered
|
||||
image: ":/icons/icons/ui/alert.svg"
|
||||
|
||||
onClicked: {
|
||||
topBar.filterNotifications = !topBar.filterNotifications
|
||||
}
|
||||
}
|
||||
ImageButton {
|
||||
id: pinButton
|
||||
|
||||
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 3
|
||||
Layout.column: 4
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.row: 1
|
||||
|
|
@ -160,7 +178,7 @@ Pane {
|
|||
}
|
||||
AbstractButton {
|
||||
id: memberButton
|
||||
Layout.column: 4
|
||||
Layout.column: 5
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.row: 1
|
||||
|
|
@ -200,7 +218,7 @@ Pane {
|
|||
property bool searchActive: false
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 5
|
||||
Layout.column: 6
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.row: 1
|
||||
|
|
@ -224,7 +242,7 @@ Pane {
|
|||
id: roomOptionsButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.column: 6
|
||||
Layout.column: 7
|
||||
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
|
||||
Layout.row: 1
|
||||
|
|
@ -273,7 +291,7 @@ Pane {
|
|||
id: pinnedMessages
|
||||
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
Layout.columnSpan: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
|
||||
Layout.row: 3
|
||||
|
|
@ -312,7 +330,7 @@ Pane {
|
|||
ImageButton {
|
||||
id: deletePinButton
|
||||
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignRight
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
ToolTip.text: qsTr("Unpin")
|
||||
|
|
@ -330,7 +348,7 @@ Pane {
|
|||
id: widgets
|
||||
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
Layout.columnSpan: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
|
||||
Layout.row: 4
|
||||
|
|
@ -356,7 +374,7 @@ Pane {
|
|||
id: searchField
|
||||
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 4
|
||||
Layout.columnSpan: 5
|
||||
Layout.fillWidth: true
|
||||
Layout.row: 5
|
||||
enabled: visible
|
||||
|
|
@ -378,6 +396,7 @@ Pane {
|
|||
searchString = "";
|
||||
searchButton.searchActive = false;
|
||||
searchField.text = "";
|
||||
filterNotifications = false;
|
||||
}
|
||||
|
||||
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
|
||||
|
|
|
|||
|
|
@ -7,15 +7,16 @@ import QtQuick.Controls
|
|||
import im.nheko
|
||||
|
||||
Image {
|
||||
required property int powerlevel
|
||||
required property var permissions
|
||||
required property var powerlevel
|
||||
required property Permissions permissions
|
||||
|
||||
readonly property bool isV12Creator: permissions ? permissions.creatorLevel() == powerlevel : false
|
||||
readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false
|
||||
readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
|
||||
readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false
|
||||
|
||||
readonly property string sourceUrl: {
|
||||
if (isAdmin)
|
||||
if (isAdmin || isV12Creator)
|
||||
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?";
|
||||
else if (isModerator)
|
||||
return "image://colorimage/:/icons/icons/ui/ribbon.svg?";
|
||||
|
|
@ -26,12 +27,15 @@ Image {
|
|||
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
|
||||
ToolTip.visible: ma.hovered
|
||||
ToolTip.text: {
|
||||
if (isAdmin)
|
||||
return qsTr("Administrator: %1").arg(powerlevel);
|
||||
let pl = powerlevel.toLocaleString(Qt.locale(), "f", 0);
|
||||
if (isV12Creator)
|
||||
return qsTr("Creator");
|
||||
else if (isAdmin)
|
||||
return qsTr("Administrator (%1)").arg(pl)
|
||||
else if (isModerator)
|
||||
return qsTr("Moderator: %1").arg(powerlevel);
|
||||
return qsTr("Moderator: %1").arg(pl);
|
||||
else
|
||||
return qsTr("User: %1").arg(powerlevel);
|
||||
return qsTr("User: %1").arg(pl);
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import im.nheko
|
||||
import "../"
|
||||
|
||||
|
|
@ -21,7 +22,11 @@ AbstractButton {
|
|||
property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : ""
|
||||
property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : ""
|
||||
implicitHeight: replyContainer.height
|
||||
implicitWidth: replyContainer.implicitWidth
|
||||
implicitWidth: replyContainer.implicitWidth + leftPadding + rightPadding
|
||||
|
||||
leftPadding: 4 + Nheko.paddingSmall
|
||||
rightPadding: Nheko.paddingSmall
|
||||
|
||||
required property int maxWidth
|
||||
property bool limitHeight: false
|
||||
|
||||
|
|
@ -31,14 +36,14 @@ AbstractButton {
|
|||
}
|
||||
|
||||
onClicked: {
|
||||
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight);
|
||||
let link = timelineEvent.main.linkAt != undefined && timelineEvent.main.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight);
|
||||
if (link) {
|
||||
Nheko.openLink(link)
|
||||
} else {
|
||||
room.showEvent(r.eventId)
|
||||
}
|
||||
}
|
||||
onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight), r.eventId)
|
||||
onPressAndHold: replyContextMenu.show(timelineEvent.main.copyText, timelineEvent.main.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight), r.eventId)
|
||||
|
||||
contentItem: TimelineEvent {
|
||||
id: timelineEvent
|
||||
|
|
@ -51,49 +56,37 @@ AbstractButton {
|
|||
maxWidth: r.maxWidth
|
||||
limitAsReply: r.limitHeight
|
||||
|
||||
//height: replyContainer.implicitHeight
|
||||
data: Row {
|
||||
data: Column {
|
||||
id: replyContainer
|
||||
|
||||
spacing: Nheko.paddingSmall
|
||||
spacing: 0
|
||||
|
||||
clip: r.limitHeight
|
||||
|
||||
height: r.limitHeight ? Math.min( timelineEvent.main?.height, timelineView.height / 10) + Nheko.paddingSmall + usernameBtn.height : undefined
|
||||
|
||||
Rectangle {
|
||||
id: colorline
|
||||
AbstractButton {
|
||||
id: usernameBtn
|
||||
|
||||
width: 4
|
||||
height: content.height
|
||||
|
||||
color: TimelineManager.userColor(r.userId, palette.base)
|
||||
}
|
||||
|
||||
Column {
|
||||
id: content
|
||||
spacing: 0
|
||||
|
||||
AbstractButton {
|
||||
id: usernameBtn
|
||||
|
||||
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
text: r.userName
|
||||
color: r.userColor
|
||||
textFormat: Text.RichText
|
||||
width: timelineEvent.main?.width
|
||||
}
|
||||
onClicked: room.openUserProfile(r.userId)
|
||||
contentItem: Label {
|
||||
id: userName_
|
||||
// HACK: To ensure the username gets rendered in Qt 6.9.2,
|
||||
// we need to always have some text in here. The name
|
||||
// should never be empty, since it falls to the mxid, but
|
||||
// if we have no text there, Qt culls the item, before we
|
||||
// fill it...
|
||||
text: r.userName || "."
|
||||
color: r.userColor
|
||||
textFormat: Text.RichText
|
||||
width: timelineEvent.main?.width
|
||||
}
|
||||
|
||||
data: [
|
||||
usernameBtn, timelineEvent.main,
|
||||
]
|
||||
onClicked: room.openUserProfile(r.userId)
|
||||
}
|
||||
|
||||
data: [
|
||||
usernameBtn, timelineEvent.main,
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
|
|
@ -103,6 +96,16 @@ AbstractButton {
|
|||
property color userColor: TimelineManager.userColor(r.userId, palette.base)
|
||||
property color bgColor: palette.base
|
||||
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
|
||||
id: colorline
|
||||
color: backgroundItem.userColor
|
||||
width: 4
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,17 @@ ApplicationWindow {
|
|||
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker)
|
||||
onToggled: hiddenEvents.toggle(MtxEvent.Sticker)
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Allowed server changes")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.ServerAcl)
|
||||
onToggled: hiddenEvents.toggle(MtxEvent.ServerAcl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,14 +94,17 @@ ApplicationWindow {
|
|||
Text {
|
||||
visible: !model.isType;
|
||||
text: {
|
||||
let pl = model.powerlevel.toLocaleString(Qt.locale(), "f", 0);
|
||||
if (editingModel.creatorLevel == model.powerlevel)
|
||||
return qsTr("Creator")
|
||||
if (editingModel.adminLevel == model.powerlevel)
|
||||
return qsTr("Administrator (%1)").arg(model.powerlevel)
|
||||
return qsTr("Administrator (%1)").arg(pl)
|
||||
else if (editingModel.moderatorLevel == model.powerlevel)
|
||||
return qsTr("Moderator (%1)").arg(model.powerlevel)
|
||||
return qsTr("Moderator (%1)").arg(pl)
|
||||
else if (editingModel.defaultUserLevel == model.powerlevel)
|
||||
return qsTr("User (%1)").arg(model.powerlevel)
|
||||
return qsTr("User (%1)").arg(pl)
|
||||
else
|
||||
return qsTr("Custom (%1)").arg(model.powerlevel)
|
||||
return qsTr("Custom (%1)").arg(pl)
|
||||
}
|
||||
color: palette.text
|
||||
}
|
||||
|
|
@ -138,7 +141,7 @@ ApplicationWindow {
|
|||
|
||||
color: palette.text
|
||||
|
||||
Keys.onPressed: {
|
||||
Keys.onPressed: event => {
|
||||
if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
editingModel.types.add(typeEntry.index, typeEntry.text)
|
||||
typeEntry.visible = false;
|
||||
|
|
@ -334,12 +337,17 @@ ApplicationWindow {
|
|||
Text {
|
||||
visible: !model.isUser;
|
||||
text: {
|
||||
let pl = model.powerlevel.toLocaleString(Qt.locale(), "f", 0);
|
||||
if (editingModel.creatorLevel == model.powerlevel)
|
||||
return qsTr("Creator")
|
||||
if (editingModel.adminLevel == model.powerlevel)
|
||||
return qsTr("Administrator (%1)").arg(model.powerlevel)
|
||||
return qsTr("Administrator (%1)").arg(pl)
|
||||
else if (editingModel.moderatorLevel == model.powerlevel)
|
||||
return qsTr("Moderator (%1)").arg(model.powerlevel)
|
||||
return qsTr("Moderator (%1)").arg(pl)
|
||||
else if (editingModel.defaultUserLevel == model.powerlevel)
|
||||
return qsTr("User (%1)").arg(pl)
|
||||
else
|
||||
return qsTr("Custom (%1)").arg(model.powerlevel)
|
||||
return qsTr("Custom (%1)").arg(pl)
|
||||
}
|
||||
color: palette.text
|
||||
}
|
||||
|
|
@ -349,7 +357,7 @@ ApplicationWindow {
|
|||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: 2
|
||||
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
|
||||
visible: !model.isUser || model.removeable
|
||||
visible: (!model.isUser || model.removeable) && model.powerlevel != editingModel.creatorLevel
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
|
||||
|
|
|
|||
|
|
@ -304,13 +304,16 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
ComboBox {
|
||||
id: notificationsCombo
|
||||
Layout.fillWidth: true
|
||||
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
|
||||
currentIndex: roomSettings.notifications
|
||||
onActivated: (index) => {
|
||||
roomSettings.changeNotifications(index);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
WheelHandler{} // suppress scrolling changing values
|
||||
|
||||
// Disable built-in wheel handling unless focused
|
||||
wheelEnabled: activeFocus
|
||||
}
|
||||
|
||||
Label {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Rectangle {
|
|||
roleValue: UserSettingsModel.Toggle
|
||||
ToggleButton {
|
||||
checked: model.value
|
||||
onCheckedChanged: model.value = checked
|
||||
onClicked: model.value = checked
|
||||
enabled: model.enabled
|
||||
}
|
||||
}
|
||||
|
|
@ -100,10 +100,13 @@ Rectangle {
|
|||
model: r.model.values
|
||||
currentIndex: r.model.value
|
||||
width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium)
|
||||
onCurrentIndexChanged: r.model.value = currentIndex
|
||||
onActivated: {
|
||||
r.model.value = currentIndex
|
||||
}
|
||||
implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted
|
||||
|
||||
WheelHandler{} // suppress scrolling changing values
|
||||
// Disable built-in wheel handling unless focused
|
||||
wheelEnabled: activeFocus
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
|
|
@ -118,7 +121,7 @@ Rectangle {
|
|||
onValueChanged: model.value = value
|
||||
editable: true
|
||||
|
||||
WheelHandler{} // suppress scrolling changing values
|
||||
wheelEnabled: activeFocus
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
|
|
@ -135,7 +138,7 @@ Rectangle {
|
|||
to: model.valueUpperBound * div
|
||||
stepSize: model.valueStep * div
|
||||
value: model.value * div
|
||||
onValueChanged: model.value = value/div
|
||||
onValueModified: model.value = value/div
|
||||
editable: true
|
||||
|
||||
property real realValue: value / div
|
||||
|
|
@ -153,7 +156,7 @@ Rectangle {
|
|||
return Number.fromLocaleString(locale, text) * spinbox.div
|
||||
}
|
||||
|
||||
WheelHandler{} // suppress scrolling changing values
|
||||
wheelEnabled: activeFocus
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
|
|
@ -272,6 +275,5 @@ Rectangle {
|
|||
ToolTip.text: qsTr("Back")
|
||||
onClicked: mainWindow.pop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<RCC>
|
||||
<qresource prefix="/icons">
|
||||
<file>icons/ui/add-square-button.svg</file>
|
||||
<file>icons/ui/alert.svg</file>
|
||||
<file>icons/ui/angle-arrow-left.svg</file>
|
||||
<file>icons/ui/attach.svg</file>
|
||||
<file>icons/ui/ban.svg</file>
|
||||
|
|
|
|||
|
|
@ -2479,11 +2479,12 @@ try {
|
|||
}
|
||||
}
|
||||
|
||||
updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString();
|
||||
updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString();
|
||||
updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
||||
updatedInfo.version = getRoomVersion(txn, statesdb).toStdString();
|
||||
updatedInfo.is_space = getRoomIsSpace(txn, statesdb);
|
||||
updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString();
|
||||
updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString();
|
||||
updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
|
||||
updatedInfo.version = getRoomVersion(txn, statesdb).toStdString();
|
||||
updatedInfo.is_space = getRoomIsSpace(txn, statesdb);
|
||||
updatedInfo.is_tombstoned = getRoomIsTombstoned(txn, statesdb);
|
||||
|
||||
updatedInfo.notification_count = room.second.unread_notifications.notification_count;
|
||||
updatedInfo.highlight_count = room.second.unread_notifications.highlight_count;
|
||||
|
|
@ -3623,7 +3624,7 @@ Cache::getRoomIsTombstoned(lmdb::txn &txn, lmdb::dbi &statesdb)
|
|||
using namespace mtx::events::state;
|
||||
|
||||
std::string_view event;
|
||||
bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event);
|
||||
bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTombstone), event);
|
||||
|
||||
if (res) {
|
||||
try {
|
||||
|
|
@ -4621,12 +4622,14 @@ Cache::updateSpaces(lmdb::txn &txn,
|
|||
event.state_key.at(0) == '!') {
|
||||
const std::string &space = event.state_key;
|
||||
|
||||
auto create = getStateEvent<mtx::events::state::Create>(txn, space)
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
auto pls = getStateEvent<mtx::events::state::PowerLevels>(txn, space);
|
||||
|
||||
if (!pls)
|
||||
continue;
|
||||
|
||||
if (pls->content.user_level(event.sender) >=
|
||||
if (pls->content.user_level(event.sender, create) >=
|
||||
pls->content.state_level(space_event_type)) {
|
||||
db->spacesChildren.put(txn, space, room);
|
||||
db->spacesParents.put(txn, room, space);
|
||||
|
|
@ -4635,7 +4638,7 @@ Cache::updateSpaces(lmdb::txn &txn,
|
|||
room,
|
||||
space,
|
||||
event.sender,
|
||||
pls->content.user_level(event.sender),
|
||||
pls->content.user_level(event.sender, create),
|
||||
pls->content.state_level(space_event_type));
|
||||
}
|
||||
}
|
||||
|
|
@ -4866,23 +4869,19 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes
|
|||
int64_t min_event_level = std::numeric_limits<int64_t>::max();
|
||||
int64_t user_level = std::numeric_limits<int64_t>::min();
|
||||
|
||||
std::string_view event;
|
||||
bool res = db_.get(txn, to_string(EventType::RoomPowerLevels), event);
|
||||
try {
|
||||
StateEvent<Create> create = getStateEvent<mtx::events::state::Create>(txn, room_id)
|
||||
.value_or(StateEvent<Create>{});
|
||||
StateEvent<PowerLevels> pls =
|
||||
getStateEvent<mtx::events::state::PowerLevels>(txn, room_id)
|
||||
.value_or(StateEvent<PowerLevels>{});
|
||||
|
||||
if (res) {
|
||||
try {
|
||||
StateEvent<PowerLevels> msg =
|
||||
nlohmann::json::parse(std::string_view(event.data(), event.size()))
|
||||
.get<StateEvent<PowerLevels>>();
|
||||
user_level = pls.content.user_level(user_id, create);
|
||||
|
||||
user_level = msg.content.user_level(user_id);
|
||||
|
||||
for (const auto &ty : eventTypes)
|
||||
min_event_level =
|
||||
std::min(min_event_level, msg.content.state_level(to_string(ty)));
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
|
||||
}
|
||||
for (const auto &ty : eventTypes)
|
||||
min_event_level = std::min(min_event_level, pls.content.state_level(to_string(ty)));
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what());
|
||||
}
|
||||
|
||||
return user_level >= min_event_level;
|
||||
|
|
@ -6410,6 +6409,7 @@ NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::JoinRules)
|
|||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Name)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PinnedEvents)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PowerLevels)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Create)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::ServerAcl)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Child)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Parent)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
|
|||
auto string1 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
|
||||
.toString()
|
||||
.normalized(QString::NormalizationForm_KD)
|
||||
.toCaseFolded();
|
||||
if (!string1.isEmpty()) {
|
||||
trie_.insert<ElementRank::first>(string1.toUcs4(), i);
|
||||
|
|
@ -53,6 +54,7 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
|
|||
auto string2 = sourceModel()
|
||||
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
|
||||
.toString()
|
||||
.normalized(QString::NormalizationForm_KD)
|
||||
.toCaseFolded();
|
||||
if (!string2.isEmpty()) {
|
||||
trie_.insert<ElementRank::first>(string2.toUcs4(), i);
|
||||
|
|
@ -73,7 +75,7 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
|
|||
&CompletionProxyModel::newSearchString,
|
||||
this,
|
||||
[this](const QString &s) {
|
||||
searchString_ = s.toCaseFolded();
|
||||
searchString_ = s.normalized(QString::NormalizationForm_KD).toCaseFolded();
|
||||
invalidate();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ MemberListBackend::MemberListBackend(const QString &room_id, QObject *parent)
|
|||
->getStateEvent<mtx::events::state::PowerLevels>(room_id_.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content}
|
||||
, create_{cache::client()
|
||||
->getStateEvent<mtx::events::state::Create>(room_id_.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{})}
|
||||
{
|
||||
try {
|
||||
info_ = cache::singleRoomInfo(room_id_.toStdString());
|
||||
|
|
@ -92,7 +95,7 @@ MemberListBackend::data(const QModelIndex &index, int role) const
|
|||
}
|
||||
case Powerlevel:
|
||||
return static_cast<qlonglong>(
|
||||
powerLevels_.user_level(m_memberList[index.row()].first.user_id.toStdString()));
|
||||
powerLevels_.user_level(m_memberList[index.row()].first.user_id.toStdString(), create_));
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ private:
|
|||
bool loadingMoreMembers_{false};
|
||||
|
||||
mtx::events::state::PowerLevels powerLevels_;
|
||||
mtx::events::StateEvent<mtx::events::state::Create> create_;
|
||||
|
||||
friend class MemberList;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -299,12 +299,10 @@ MxcImageProvider::download(const QString &id,
|
|||
"/media_cache",
|
||||
fileName);
|
||||
QDir().mkpath(fileInfo.absolutePath());
|
||||
QFile f(fileInfo.absoluteFilePath());
|
||||
|
||||
if (fileInfo.exists()) {
|
||||
if (fileInfo.exists() && f.open(QIODevice::ReadOnly)) {
|
||||
if (encryptionInfo) {
|
||||
QFile f(fileInfo.absoluteFilePath());
|
||||
f.open(QIODevice::ReadOnly);
|
||||
|
||||
QByteArray fileData = f.readAll();
|
||||
auto tempData = mtx::crypto::to_string(
|
||||
mtx::crypto::decrypt_file(fileData.toStdString(), encryptionInfo.value()));
|
||||
|
|
|
|||
|
|
@ -17,12 +17,15 @@
|
|||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
|
||||
PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
QObject *parent)
|
||||
PowerlevelsTypeListModel::PowerlevelsTypeListModel(
|
||||
const std::string &rid,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &create,
|
||||
QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, room_id(rid)
|
||||
, powerLevels_(pl)
|
||||
, create_(create)
|
||||
{
|
||||
std::set<mtx::events::state::power_level_t> seen_levels;
|
||||
for (const auto &[type, level] : powerLevels_.events) {
|
||||
|
|
@ -40,6 +43,9 @@ PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid,
|
|||
seen_levels.insert(level);
|
||||
}
|
||||
}
|
||||
if (create_.content.room_version_creators_with_infinite_power()) {
|
||||
seen_levels.insert(mtx::events::state::Creator);
|
||||
}
|
||||
|
||||
for (const auto &level : {
|
||||
powerLevels_.events_default,
|
||||
|
|
@ -354,12 +360,15 @@ PowerlevelsTypeListModel::moveRows(const QModelIndex &,
|
|||
return true;
|
||||
}
|
||||
|
||||
PowerlevelsUserListModel::PowerlevelsUserListModel(const std::string &rid,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
QObject *parent)
|
||||
PowerlevelsUserListModel::PowerlevelsUserListModel(
|
||||
const std::string &rid,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &create,
|
||||
QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, room_id(rid)
|
||||
, powerLevels_(pl)
|
||||
, create_(create)
|
||||
{
|
||||
std::set<mtx::events::state::power_level_t> seen_levels;
|
||||
for (const auto &[user, level] : powerLevels_.users) {
|
||||
|
|
@ -378,6 +387,16 @@ PowerlevelsUserListModel::PowerlevelsUserListModel(const std::string &rid,
|
|||
}
|
||||
}
|
||||
|
||||
if (create_.content.room_version_creators_with_infinite_power()) {
|
||||
users.push_back(Entry{"", mtx::events::state::Creator});
|
||||
seen_levels.insert(mtx::events::state::Creator);
|
||||
|
||||
users.push_back(Entry{create_.sender, mtx::events::state::Creator});
|
||||
for (const auto &user : create.content.additional_creators) {
|
||||
users.push_back(Entry{user, mtx::events::state::Creator});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &level : {
|
||||
powerLevels_.events_default,
|
||||
powerLevels_.state_default,
|
||||
|
|
@ -408,7 +427,7 @@ PowerlevelsUserListModel::toUsers() const
|
|||
{
|
||||
std::map<std::string, mtx::events::state::power_level_t, std::less<>> m;
|
||||
for (const auto &[key, pl] : std::as_const(users))
|
||||
if (key.size() > 0 && key.at(0) == '@')
|
||||
if (key.size() > 0 && key.at(0) == '@' && pl != mtx::events::state::Creator)
|
||||
m[key] = pl;
|
||||
return m;
|
||||
}
|
||||
|
|
@ -459,7 +478,7 @@ PowerlevelsUserListModel::data(const QModelIndex &index, int role) const
|
|||
case IsUser:
|
||||
return !user.mxid.empty();
|
||||
case Moveable:
|
||||
return !user.mxid.empty();
|
||||
return !user.mxid.empty() && user.pl != mtx::events::state::Creator;
|
||||
case Removeable:
|
||||
return !user.mxid.empty() && user.mxid.find('.') != std::string::npos;
|
||||
}
|
||||
|
|
@ -554,7 +573,15 @@ PowerlevelsUserListModel::moveRows(const QModelIndex &,
|
|||
if (users.at(sourceRow).mxid.empty())
|
||||
return false;
|
||||
|
||||
auto pl = users.at(destinationChild > 0 ? destinationChild - 1 : 0).pl;
|
||||
if (users.at(sourceRow).pl == mtx::events::state::Creator)
|
||||
return false;
|
||||
if (users.at(destinationChild).pl == mtx::events::state::Creator)
|
||||
return false;
|
||||
|
||||
auto pl = users.at(destinationChild > 0 ? destinationChild - 1 : 0).pl;
|
||||
if (pl == mtx::events::state::Creator)
|
||||
return false;
|
||||
|
||||
auto sourceItem = users.takeAt(sourceRow);
|
||||
sourceItem.pl = pl;
|
||||
|
||||
|
|
@ -577,9 +604,12 @@ PowerlevelEditingModels::PowerlevelEditingModels(QString room_id, QObject *paren
|
|||
->getStateEvent<mtx::events::state::PowerLevels>(room_id.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content)
|
||||
, types_(room_id.toStdString(), powerLevels_, this)
|
||||
, users_(room_id.toStdString(), powerLevels_, this)
|
||||
, spaces_(room_id.toStdString(), powerLevels_, this)
|
||||
, create_(cache::client()
|
||||
->getStateEvent<mtx::events::state::Create>(room_id.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{}))
|
||||
, types_(room_id.toStdString(), powerLevels_, create_, this)
|
||||
, users_(room_id.toStdString(), powerLevels_, create_, this)
|
||||
, spaces_(room_id.toStdString(), powerLevels_, create_, this)
|
||||
, room_id_(room_id.toStdString())
|
||||
{
|
||||
connect(&types_,
|
||||
|
|
@ -678,16 +708,18 @@ samePl(const mtx::events::state::PowerLevels &a, const mtx::events::state::Power
|
|||
b.redact);
|
||||
}
|
||||
|
||||
PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
QObject *parent)
|
||||
PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(
|
||||
const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &create,
|
||||
QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, room_id(std::move(room_id_))
|
||||
, oldPowerLevels_(std::move(pl))
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
spaces.push_back(Entry{room_id, oldPowerLevels_, true});
|
||||
spaces.push_back(Entry{room_id, oldPowerLevels_, create, true});
|
||||
|
||||
std::unordered_set<std::string> visited;
|
||||
|
||||
|
|
@ -703,10 +735,16 @@ PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_i
|
|||
cache::client()->getStateEvent<mtx::events::state::space::Parent>(s, space);
|
||||
if (parent && parent->content.via && !parent->content.via->empty() &&
|
||||
parent->content.canonical) {
|
||||
auto parentPl = cache::client()->getStateEvent<mtx::events::state::PowerLevels>(s);
|
||||
auto childPl = cache::client()->getStateEvent<mtx::events::state::PowerLevels>(s);
|
||||
auto childCreate =
|
||||
cache::client()->getStateEvent<mtx::events::state::Create>(s).value_or(
|
||||
mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
|
||||
spaces.push_back(Entry{
|
||||
s, parentPl ? parentPl->content : mtx::events::state::PowerLevels{}, false});
|
||||
spaces.push_back(
|
||||
Entry{s,
|
||||
childPl ? childPl->content : mtx::events::state::PowerLevels{},
|
||||
childCreate,
|
||||
false});
|
||||
addChildren(s);
|
||||
}
|
||||
}
|
||||
|
|
@ -813,7 +851,7 @@ PowerlevelsSpacesListModel::data(QModelIndex const &index, int role) const
|
|||
auto entry = spaces.at(row);
|
||||
switch (role) {
|
||||
case Roles::IsEditable:
|
||||
return entry.pl.user_level(http::client()->user_id().to_string()) >=
|
||||
return entry.pl.user_level(http::client()->user_id().to_string(), entry.create) >=
|
||||
entry.pl.state_level(to_string(mtx::events::EventType::RoomPowerLevels));
|
||||
case Roles::IsDifferentFromBase:
|
||||
return !samePl(entry.pl, oldPowerLevels_);
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ public:
|
|||
Removeable,
|
||||
};
|
||||
|
||||
explicit PowerlevelsTypeListModel(const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
QObject *parent = nullptr);
|
||||
explicit PowerlevelsTypeListModel(
|
||||
const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &create,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &) const override { return static_cast<int>(types.size()); }
|
||||
|
|
@ -67,6 +69,7 @@ public:
|
|||
std::string room_id;
|
||||
QVector<Entry> types;
|
||||
mtx::events::state::PowerLevels powerLevels_;
|
||||
mtx::events::StateEvent<mtx::events::state::Create> create_;
|
||||
};
|
||||
|
||||
class PowerlevelsUserListModel final : public QAbstractListModel
|
||||
|
|
@ -88,9 +91,11 @@ public:
|
|||
Removeable,
|
||||
};
|
||||
|
||||
explicit PowerlevelsUserListModel(const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
QObject *parent = nullptr);
|
||||
explicit PowerlevelsUserListModel(
|
||||
const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &create,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &) const override { return static_cast<int>(users.size()); }
|
||||
|
|
@ -121,6 +126,7 @@ public:
|
|||
std::string room_id;
|
||||
QVector<Entry> users;
|
||||
mtx::events::state::PowerLevels powerLevels_;
|
||||
mtx::events::StateEvent<mtx::events::state::Create> create_;
|
||||
};
|
||||
|
||||
class PowerlevelsSpacesListModel final : public QAbstractListModel
|
||||
|
|
@ -147,9 +153,11 @@ public:
|
|||
ApplyPermissions,
|
||||
};
|
||||
|
||||
explicit PowerlevelsSpacesListModel(const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
QObject *parent = nullptr);
|
||||
explicit PowerlevelsSpacesListModel(
|
||||
const std::string &room_id_,
|
||||
const mtx::events::state::PowerLevels &pl,
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &create,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &) const override { return static_cast<int>(spaces.size()); }
|
||||
|
|
@ -183,6 +191,7 @@ public:
|
|||
|
||||
std::string roomid;
|
||||
mtx::events::state::PowerLevels pl;
|
||||
mtx::events::StateEvent<mtx::events::state::Create> create;
|
||||
bool apply = false;
|
||||
};
|
||||
|
||||
|
|
@ -203,6 +212,7 @@ class PowerlevelEditingModels final : public QObject
|
|||
Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT)
|
||||
Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT)
|
||||
Q_PROPERTY(PowerlevelsSpacesListModel *spaces READ spaces CONSTANT)
|
||||
Q_PROPERTY(qlonglong creatorLevel READ creatorLevel CONSTANT)
|
||||
Q_PROPERTY(qlonglong adminLevel READ adminLevel NOTIFY adminLevelChanged)
|
||||
Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel NOTIFY moderatorLevelChanged)
|
||||
Q_PROPERTY(qlonglong defaultUserLevel READ defaultUserLevel NOTIFY defaultUserLevelChanged)
|
||||
|
|
@ -222,6 +232,7 @@ public:
|
|||
PowerlevelsUserListModel *users() { return &users_; }
|
||||
PowerlevelsTypeListModel *types() { return &types_; }
|
||||
PowerlevelsSpacesListModel *spaces() { return &spaces_; }
|
||||
qlonglong creatorLevel() const { return mtx::events::state::Creator; }
|
||||
qlonglong adminLevel() const
|
||||
{
|
||||
return powerLevels_.state_level(to_string(mtx::events::EventType::RoomPowerLevels));
|
||||
|
|
@ -235,6 +246,7 @@ public:
|
|||
Q_INVOKABLE void addRole(int pl);
|
||||
|
||||
mtx::events::state::PowerLevels powerLevels_;
|
||||
mtx::events::StateEvent<mtx::events::state::Create> create_;
|
||||
PowerlevelsTypeListModel types_;
|
||||
PowerlevelsUserListModel users_;
|
||||
PowerlevelsSpacesListModel spaces_;
|
||||
|
|
|
|||
|
|
@ -107,13 +107,18 @@ TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
|
|||
QMenu *menu = new QMenu();
|
||||
setContextMenu(menu);
|
||||
|
||||
viewAction_ = new QAction(tr("Show"), this);
|
||||
quitAction_ = new QAction(tr("Quit"), this);
|
||||
toggleAction_ = new QAction(tr("Show"), this);
|
||||
quitAction_ = new QAction(tr("Quit"), this);
|
||||
|
||||
connect(viewAction_, &QAction::triggered, parent, &QWindow::show);
|
||||
connect(parent, &QWindow::visibleChanged, toggleAction_, [=, this] {
|
||||
toggleAction_->setText(tr(parent->isVisible() ? "Hide" : "Show"));
|
||||
});
|
||||
connect(toggleAction_, &QAction::triggered, parent, [=] {
|
||||
parent->isVisible() ? parent->hide() : parent->show();
|
||||
});
|
||||
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
|
||||
|
||||
menu->addAction(viewAction_);
|
||||
menu->addAction(toggleAction_);
|
||||
menu->addAction(quitAction_);
|
||||
|
||||
QString toolTip = QLatin1String("nheko");
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public slots:
|
|||
void setUnreadCount(int count);
|
||||
|
||||
private:
|
||||
QAction *viewAction_;
|
||||
QAction *toggleAction_;
|
||||
QAction *quitAction_;
|
||||
|
||||
int previousCount = 0;
|
||||
|
|
|
|||
|
|
@ -25,10 +25,24 @@
|
|||
|
||||
#include "config/nheko.h"
|
||||
|
||||
QStringList themes{
|
||||
QStringLiteral("light"),
|
||||
QStringLiteral("dark"),
|
||||
QStringLiteral("system"),
|
||||
};
|
||||
|
||||
QSharedPointer<UserSettings> UserSettings::instance_;
|
||||
|
||||
UserSettings::UserSettings()
|
||||
{
|
||||
if (settings.contains("user/invert_enter_key")) {
|
||||
auto oldValue =
|
||||
(settings.value("user/invert_enter_key", false).toBool() ? SendMessageKey::ShiftEnter
|
||||
: SendMessageKey::Enter);
|
||||
settings.setValue("user/send_message_key", static_cast<int>(oldValue));
|
||||
settings.remove("user/invert_enter_key");
|
||||
}
|
||||
|
||||
connect(
|
||||
QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); });
|
||||
}
|
||||
|
|
@ -65,8 +79,13 @@ UserSettings::load(std::optional<QString> profile)
|
|||
settings.value("user/timeline/message_hover_highlight", false).toBool();
|
||||
enlargeEmojiOnlyMessages_ =
|
||||
settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool();
|
||||
markdown_ = settings.value("user/markdown_enabled", true).toBool();
|
||||
invertEnterKey_ = settings.value("user/invert_enter_key", false).toBool();
|
||||
markdown_ = settings.value("user/markdown_enabled", true).toBool();
|
||||
|
||||
auto sendMessageKey = settings.value("user/send_message_key", 0).toInt();
|
||||
if (sendMessageKey < 0 || sendMessageKey > 2)
|
||||
sendMessageKey = static_cast<int>(SendMessageKey::Enter);
|
||||
sendMessageKey_ = static_cast<SendMessageKey>(sendMessageKey);
|
||||
|
||||
bubbles_ = settings.value("user/bubbles_enabled", false).toBool();
|
||||
smallAvatars_ = settings.value("user/small_avatars_enabled", false).toBool();
|
||||
animateImagesOnHover_ = settings.value("user/animate_images_on_hover", false).toBool();
|
||||
|
|
@ -334,13 +353,12 @@ UserSettings::setMarkdown(bool state)
|
|||
}
|
||||
|
||||
void
|
||||
UserSettings::setInvertEnterKey(bool state)
|
||||
UserSettings::setSendMessageKey(SendMessageKey key)
|
||||
{
|
||||
if (state == invertEnterKey_)
|
||||
if (key == sendMessageKey_)
|
||||
return;
|
||||
|
||||
invertEnterKey_ = state;
|
||||
emit invertEnterKeyChanged(state);
|
||||
sendMessageKey_ = key;
|
||||
emit sendMessageKeyChanged(key);
|
||||
save();
|
||||
}
|
||||
|
||||
|
|
@ -640,7 +658,7 @@ UserSettings::setShowImage(ShowImage state)
|
|||
void
|
||||
UserSettings::setTheme(QString theme)
|
||||
{
|
||||
if (theme == theme_)
|
||||
if (theme == theme_ || !themes.contains(theme))
|
||||
return;
|
||||
theme_ = theme;
|
||||
save();
|
||||
|
|
@ -930,7 +948,7 @@ UserSettings::save()
|
|||
settings.setValue("group_view", groupView_);
|
||||
settings.setValue("scrollbars_in_roomlist", scrollbarsInRoomlist_);
|
||||
settings.setValue("markdown_enabled", markdown_);
|
||||
settings.setValue("invert_enter_key", invertEnterKey_);
|
||||
settings.setValue("send_message_key", static_cast<int>(sendMessageKey_));
|
||||
settings.setValue("bubbles_enabled", bubbles_);
|
||||
settings.setValue("small_avatars_enabled", smallAvatars_);
|
||||
settings.setValue("animate_images_on_hover", animateImagesOnHover_);
|
||||
|
|
@ -1044,8 +1062,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
return tr("Scrollbars in room list");
|
||||
case Markdown:
|
||||
return tr("Send messages as Markdown");
|
||||
case InvertEnterKey:
|
||||
return tr("Use shift+enter to send and enter to start a new line");
|
||||
case SendMessageKey:
|
||||
return tr("Send messages with a shortcut");
|
||||
case Bubbles:
|
||||
return tr("Enable message bubbles");
|
||||
case SmallAvatars:
|
||||
|
|
@ -1182,12 +1200,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
} else if (role == Value) {
|
||||
switch (index.row()) {
|
||||
case Theme:
|
||||
return QStringList{
|
||||
QStringLiteral("light"),
|
||||
QStringLiteral("dark"),
|
||||
QStringLiteral("system"),
|
||||
}
|
||||
.indexOf(i->theme());
|
||||
return themes.indexOf(i->theme());
|
||||
case ScaleFactor:
|
||||
return utils::scaleFactor();
|
||||
case MessageHoverHighlight:
|
||||
|
|
@ -1204,8 +1217,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
return i->scrollbarsInRoomlist();
|
||||
case Markdown:
|
||||
return i->markdown();
|
||||
case InvertEnterKey:
|
||||
return i->invertEnterKey();
|
||||
case SendMessageKey:
|
||||
return static_cast<int>(i->sendMessageKey());
|
||||
case Bubbles:
|
||||
return i->bubbles();
|
||||
case SmallAvatars:
|
||||
|
|
@ -1370,10 +1383,11 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
return tr(
|
||||
"Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain "
|
||||
"text.");
|
||||
case InvertEnterKey:
|
||||
case SendMessageKey:
|
||||
return tr(
|
||||
"Invert the behavior of the enter key in the text input, making it send the message "
|
||||
"when shift+enter is pressed and starting a new line when enter is pressed.");
|
||||
"Select what Enter key combination sends the message. Shift+Enter adds a new line, "
|
||||
"unless it has been selected, in which case Enter adds a new line instead.\n\n"
|
||||
"If an emoji picker or a mention picker is open, it is always handled first.");
|
||||
case Bubbles:
|
||||
return tr(
|
||||
"Messages get a bubble background. This also triggers some layout changes (WIP).");
|
||||
|
|
@ -1541,6 +1555,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
case CameraFrameRate:
|
||||
case Ringtone:
|
||||
case ShowImage:
|
||||
case SendMessageKey:
|
||||
return Options;
|
||||
case TimelineMaxWidth:
|
||||
case PrivacyScreenTimeout:
|
||||
|
|
@ -1555,7 +1570,6 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
case GroupView:
|
||||
case ScrollbarsInRoomlist:
|
||||
case Markdown:
|
||||
case InvertEnterKey:
|
||||
case Bubbles:
|
||||
case SmallAvatars:
|
||||
case AnimateImagesOnHover:
|
||||
|
|
@ -1674,6 +1688,12 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
|||
tr("Only in private rooms"),
|
||||
tr("Never"),
|
||||
};
|
||||
case SendMessageKey:
|
||||
return QStringList{
|
||||
tr("Enter"),
|
||||
tr("Shift+Enter"),
|
||||
tr("Ctrl+Enter"),
|
||||
};
|
||||
case Microphone:
|
||||
return vecToList(CallDevices::instance().names(false, i->microphone().toStdString()));
|
||||
case Camera:
|
||||
|
|
@ -1741,14 +1761,10 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
|
|||
if (role == Value) {
|
||||
switch (index.row()) {
|
||||
case Theme: {
|
||||
if (value == 0) {
|
||||
i->setTheme("light");
|
||||
return true;
|
||||
} else if (value == 1) {
|
||||
i->setTheme("dark");
|
||||
return true;
|
||||
} else if (value == 2) {
|
||||
i->setTheme("system");
|
||||
auto idx = value.toInt();
|
||||
|
||||
if (idx >= 0 && idx < themes.size()) {
|
||||
i->setTheme(themes[idx]);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
|
|
@ -1819,12 +1835,14 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
|
|||
} else
|
||||
return false;
|
||||
}
|
||||
case InvertEnterKey: {
|
||||
if (value.userType() == QMetaType::Bool) {
|
||||
i->setInvertEnterKey(value.toBool());
|
||||
return true;
|
||||
} else
|
||||
case SendMessageKey: {
|
||||
auto newKey = value.toInt();
|
||||
if (newKey < 0 ||
|
||||
QMetaEnum::fromType<UserSettings::SendMessageKey>().keyCount() <= newKey)
|
||||
return false;
|
||||
|
||||
i->setSendMessageKey(static_cast<UserSettings::SendMessageKey>(newKey));
|
||||
return true;
|
||||
}
|
||||
case Bubbles: {
|
||||
if (value.userType() == QMetaType::Bool) {
|
||||
|
|
@ -2309,8 +2327,8 @@ UserSettingsModel::UserSettingsModel(QObject *p)
|
|||
connect(s.get(), &UserSettings::markdownChanged, this, [this]() {
|
||||
emit dataChanged(index(Markdown), index(Markdown), {Value});
|
||||
});
|
||||
connect(s.get(), &UserSettings::invertEnterKeyChanged, this, [this]() {
|
||||
emit dataChanged(index(InvertEnterKey), index(InvertEnterKey), {Value});
|
||||
connect(s.get(), &UserSettings::sendMessageKeyChanged, this, [this]() {
|
||||
emit dataChanged(index(SendMessageKey), index(SendMessageKey), {Value});
|
||||
});
|
||||
connect(s.get(), &UserSettings::bubblesChanged, this, [this]() {
|
||||
emit dataChanged(index(Bubbles), index(Bubbles), {Value});
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ class UserSettings final : public QObject
|
|||
Q_PROPERTY(bool scrollbarsInRoomlist READ scrollbarsInRoomlist WRITE setScrollbarsInRoomlist
|
||||
NOTIFY scrollbarsInRoomlistChanged)
|
||||
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
|
||||
Q_PROPERTY(
|
||||
bool invertEnterKey READ invertEnterKey WRITE setInvertEnterKey NOTIFY invertEnterKeyChanged)
|
||||
Q_PROPERTY(SendMessageKey sendMessageKey READ sendMessageKey WRITE setSendMessageKey NOTIFY
|
||||
sendMessageKeyChanged)
|
||||
Q_PROPERTY(bool bubbles READ bubbles WRITE setBubbles NOTIFY bubblesChanged)
|
||||
Q_PROPERTY(bool smallAvatars READ smallAvatars WRITE setSmallAvatars NOTIFY smallAvatarsChanged)
|
||||
Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover
|
||||
|
|
@ -166,6 +166,14 @@ public:
|
|||
};
|
||||
Q_ENUM(ShowImage)
|
||||
|
||||
enum class SendMessageKey
|
||||
{
|
||||
Enter,
|
||||
ShiftEnter,
|
||||
CtrlEnter,
|
||||
};
|
||||
Q_ENUM(SendMessageKey)
|
||||
|
||||
void save();
|
||||
void load(std::optional<QString> profile);
|
||||
void applyTheme();
|
||||
|
|
@ -182,7 +190,7 @@ public:
|
|||
void setGroupView(bool state);
|
||||
void setScrollbarsInRoomlist(bool state);
|
||||
void setMarkdown(bool state);
|
||||
void setInvertEnterKey(bool state);
|
||||
void setSendMessageKey(SendMessageKey key);
|
||||
void setBubbles(bool state);
|
||||
void setSmallAvatars(bool state);
|
||||
void setAnimateImagesOnHover(bool state);
|
||||
|
|
@ -255,7 +263,7 @@ public:
|
|||
bool privacyScreen() const { return privacyScreen_; }
|
||||
int privacyScreenTimeout() const { return privacyScreenTimeout_; }
|
||||
bool markdown() const { return markdown_; }
|
||||
bool invertEnterKey() const { return invertEnterKey_; }
|
||||
SendMessageKey sendMessageKey() const { return sendMessageKey_; }
|
||||
bool bubbles() const { return bubbles_; }
|
||||
bool smallAvatars() const { return smallAvatars_; }
|
||||
bool animateImagesOnHover() const { return animateImagesOnHover_; }
|
||||
|
|
@ -328,7 +336,7 @@ signals:
|
|||
void trayChanged(bool state);
|
||||
void startInTrayChanged(bool state);
|
||||
void markdownChanged(bool state);
|
||||
void invertEnterKeyChanged(bool state);
|
||||
void sendMessageKeyChanged(SendMessageKey key);
|
||||
void bubblesChanged(bool state);
|
||||
void smallAvatarsChanged(bool state);
|
||||
void animateImagesOnHoverChanged(bool state);
|
||||
|
|
@ -399,7 +407,7 @@ private:
|
|||
bool groupView_;
|
||||
bool scrollbarsInRoomlist_;
|
||||
bool markdown_;
|
||||
bool invertEnterKey_;
|
||||
SendMessageKey sendMessageKey_;
|
||||
bool bubbles_;
|
||||
bool smallAvatars_;
|
||||
bool animateImagesOnHover_;
|
||||
|
|
@ -510,7 +518,7 @@ class UserSettingsModel : public QAbstractListModel
|
|||
TypingNotifications,
|
||||
ReadReceipts,
|
||||
Markdown,
|
||||
InvertEnterKey,
|
||||
SendMessageKey,
|
||||
Bubbles,
|
||||
SmallAvatars,
|
||||
|
||||
|
|
|
|||
|
|
@ -1453,6 +1453,9 @@ utils::roomVias(const std::string &roomid)
|
|||
auto powerlevels =
|
||||
cache::client()->getStateEvent<mtx::events::state::PowerLevels>(roomid).value_or(
|
||||
mtx::events::StateEvent<mtx::events::state::PowerLevels>{});
|
||||
auto create =
|
||||
cache::client()->getStateEvent<mtx::events::state::Create>(roomid).value_or(
|
||||
mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
auto acls = cache::client()->getStateEvent<mtx::events::state::ServerAcl>(roomid);
|
||||
|
||||
std::vector<QRegularExpression> allowedServers;
|
||||
|
|
@ -1501,6 +1504,19 @@ utils::roomVias(const std::string &roomid)
|
|||
std::set<std::string> users_with_high_pl_in_room;
|
||||
// we should pick PL > 50, but imo that is broken, so we just pick users who have admins
|
||||
// perm
|
||||
if (create.content.room_version_creators_with_infinite_power()) {
|
||||
{
|
||||
auto user = create.sender;
|
||||
auto host = mtx::identifiers::parse<mtx::identifiers::User>(user).hostname();
|
||||
if (isHostAllowed(host))
|
||||
users_with_high_pl.insert(user);
|
||||
}
|
||||
for (const auto &user : create.content.additional_creators) {
|
||||
auto host = mtx::identifiers::parse<mtx::identifiers::User>(user).hostname();
|
||||
if (isHostAllowed(host))
|
||||
users_with_high_pl.insert(user);
|
||||
}
|
||||
}
|
||||
for (const auto &user : powerlevels.content.users) {
|
||||
if (user.second >= powerlevels.content.events_default &&
|
||||
user.second >= powerlevels.content.state_default) {
|
||||
|
|
@ -1525,12 +1541,13 @@ utils::roomVias(const std::string &roomid)
|
|||
});
|
||||
|
||||
// add the highest powerlevel user
|
||||
auto max_pl_user = std::max_element(
|
||||
users_with_high_pl_in_room.begin(),
|
||||
users_with_high_pl_in_room.end(),
|
||||
[&pl_content = powerlevels.content](const std::string &a, const std::string &b) {
|
||||
return pl_content.user_level(a) < pl_content.user_level(b);
|
||||
});
|
||||
auto max_pl_user = std::max_element(users_with_high_pl_in_room.begin(),
|
||||
users_with_high_pl_in_room.end(),
|
||||
[&pl_content = powerlevels.content, &create](
|
||||
const std::string &a, const std::string &b) {
|
||||
return pl_content.user_level(a, create) <
|
||||
pl_content.user_level(b, create);
|
||||
});
|
||||
if (max_pl_user != users_with_high_pl_in_room.end()) {
|
||||
auto host =
|
||||
mtx::identifiers::parse<mtx::identifiers::User>(*max_pl_user).hostname();
|
||||
|
|
@ -1705,11 +1722,15 @@ utils::updateSpaceVias()
|
|||
|
||||
auto spaceid = roomid.toStdString();
|
||||
|
||||
auto create = cache::client()->getStateEvent<mtx::events::state::Create>(spaceid).value_or(
|
||||
mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
|
||||
if (auto pl = cache::client()
|
||||
->getStateEvent<mtx::events::state::PowerLevels>(spaceid)
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content;
|
||||
pl.user_level(us) < pl.state_level(to_string(mtx::events::EventType::SpaceChild)))
|
||||
pl.user_level(us, create) <
|
||||
pl.state_level(to_string(mtx::events::EventType::SpaceChild)))
|
||||
continue;
|
||||
|
||||
auto children = cache::client()->getChildRoomIds(spaceid);
|
||||
|
|
@ -1748,12 +1769,16 @@ utils::updateSpaceVias()
|
|||
parent->origin_server_ts < weekAgo &&
|
||||
// ignore unset spaces
|
||||
(parent->content.via && !parent->content.via->empty())) {
|
||||
auto childCreate =
|
||||
cache::client()->getStateEvent<mtx::events::state::Create>(spaceid).value_or(
|
||||
mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
|
||||
if (auto pl =
|
||||
cache::client()
|
||||
->getStateEvent<mtx::events::state::PowerLevels>(childid)
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content;
|
||||
pl.user_level(us) <
|
||||
pl.user_level(us, childCreate) <
|
||||
pl.state_level(to_string(mtx::events::EventType::SpaceParent)))
|
||||
continue;
|
||||
|
||||
|
|
@ -2041,11 +2066,15 @@ utils::removeExpiredEvents()
|
|||
if (!asus->globalExpiry && !getExpEv(roomid))
|
||||
continue;
|
||||
|
||||
auto create = cache::client()->getStateEvent<mtx::events::state::Create>(roomid).value_or(
|
||||
mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
|
||||
if (auto pl = cache::client()
|
||||
->getStateEvent<mtx::events::state::PowerLevels>(roomid)
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content;
|
||||
pl.user_level(us) < pl.event_level(to_string(mtx::events::EventType::RoomRedaction))) {
|
||||
pl.user_level(us, create) <
|
||||
pl.event_level(to_string(mtx::events::EventType::RoomRedaction))) {
|
||||
nhlog::net()->warn("Can't react events in {}, not running expiration.", roomid);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,6 +170,14 @@ setStatusMessage(const QString &message)
|
|||
interface.call(QDBus::NoBlock, QStringLiteral("setStatusMessage"), message);
|
||||
}
|
||||
|
||||
void
|
||||
setTheme(const QString &theme)
|
||||
{
|
||||
if (QDBusInterface interface{QStringLiteral(NHEKO_DBUS_SERVICE_NAME), QStringLiteral("/")};
|
||||
interface.isValid())
|
||||
interface.call(QDBus::NoBlock, QStringLiteral("setTheme"), theme);
|
||||
}
|
||||
|
||||
} // nheko::dbus
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ statusMessage();
|
|||
//! Sets the user's status message (if supported by the homeserver).
|
||||
void
|
||||
setStatusMessage(const QString &message);
|
||||
//! Sets the current theme (supported values: "light", "dark" or "system")
|
||||
void
|
||||
setTheme(const QString &theme);
|
||||
|
||||
QDBusArgument &
|
||||
operator<<(QDBusArgument &arg, const RoomInfoItem &item);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "timeline/RoomlistModel.h"
|
||||
#include "timeline/TimelineModel.h"
|
||||
|
||||
|
|
@ -112,6 +113,12 @@ NhekoDBusBackend::setStatusMessage(const QString &message)
|
|||
ChatPage::instance()->setStatus(message);
|
||||
}
|
||||
|
||||
void
|
||||
NhekoDBusBackend::setTheme(const QString &theme)
|
||||
{
|
||||
UserSettings::instance()->setTheme(theme);
|
||||
}
|
||||
|
||||
void
|
||||
NhekoDBusBackend::bringWindowToTop() const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ public slots:
|
|||
Q_SCRIPTABLE QString statusMessage() const;
|
||||
//! Sets the user's status message.
|
||||
Q_SCRIPTABLE void setStatusMessage(const QString &message);
|
||||
//! Sets the current theme (supported values: "light", "dark" or "system")
|
||||
Q_SCRIPTABLE void setTheme(const QString &theme);
|
||||
|
||||
private:
|
||||
void bringWindowToTop() const;
|
||||
|
|
|
|||
|
|
@ -398,17 +398,19 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey
|
|||
body[local_user][dev] = secretRequest;
|
||||
}
|
||||
|
||||
http::client()->send_to_device<mtx::events::msg::SecretRequest>(
|
||||
http::client()->generate_txn_id(),
|
||||
body,
|
||||
[secret_name](mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->error("Failed to send request cancellation "
|
||||
"for secrect "
|
||||
"'{}'",
|
||||
secret_name);
|
||||
}
|
||||
});
|
||||
if (!body.empty()) {
|
||||
http::client()->send_to_device<mtx::events::msg::SecretRequest>(
|
||||
http::client()->generate_txn_id(),
|
||||
body,
|
||||
[secret_name](mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->error("Failed to send request cancellation "
|
||||
"for secrect "
|
||||
"'{}'",
|
||||
secret_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nhlog::crypto()->info("Storing secret {}", secret_name);
|
||||
cache::client()->storeSecret(secret_name, e->content.secret);
|
||||
|
|
|
|||
|
|
@ -166,6 +166,11 @@ EventStore::EventStore(std::string room_id, QObject *)
|
|||
nhlog::ui()->debug("failing txn id '{}'", txn_id);
|
||||
cache::client()->removePendingStatus(room_id_, txn_id);
|
||||
current_txn_error_count = 0;
|
||||
|
||||
auto idx = idToIndex(txn_id);
|
||||
|
||||
if (idx)
|
||||
emit dataChanged(*idx, *idx);
|
||||
}
|
||||
}
|
||||
QTimer::singleShot(1000, this, [this]() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "Permissions.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Cache_p.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "TimelineModel.h"
|
||||
|
|
@ -22,44 +24,53 @@ Permissions::invalidate()
|
|||
->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
|
||||
.content;
|
||||
create = cache::client()
|
||||
->getStateEvent<mtx::events::state::Create>(roomId_.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::Create>{});
|
||||
}
|
||||
|
||||
bool
|
||||
Permissions::canInvite()
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >= pl.invite;
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.invite;
|
||||
return plCheck;
|
||||
}
|
||||
|
||||
bool
|
||||
Permissions::canBan()
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >= pl.ban;
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.ban;
|
||||
return plCheck;
|
||||
}
|
||||
|
||||
bool
|
||||
Permissions::canKick()
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >= pl.kick;
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.kick;
|
||||
return plCheck;
|
||||
}
|
||||
|
||||
bool
|
||||
Permissions::canRedact()
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >= pl.redact;
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >= pl.redact;
|
||||
return plCheck;
|
||||
}
|
||||
bool
|
||||
Permissions::canChange(int eventType)
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >=
|
||||
pl.state_level(to_string(
|
||||
qml_mtx_events::fromRoomEventType(static_cast<qml_mtx_events::EventType>(eventType))));
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >=
|
||||
pl.state_level(to_string(qml_mtx_events::fromRoomEventType(
|
||||
static_cast<qml_mtx_events::EventType>(eventType))));
|
||||
return plCheck;
|
||||
}
|
||||
bool
|
||||
Permissions::canSend(int eventType)
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >=
|
||||
pl.event_level(to_string(
|
||||
qml_mtx_events::fromRoomEventType(static_cast<qml_mtx_events::EventType>(eventType))));
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >=
|
||||
pl.event_level(to_string(qml_mtx_events::fromRoomEventType(
|
||||
static_cast<qml_mtx_events::EventType>(eventType))));
|
||||
return plCheck;
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -88,8 +99,9 @@ Permissions::sendLevel(int eventType)
|
|||
bool
|
||||
Permissions::canPingRoom()
|
||||
{
|
||||
return pl.user_level(http::client()->user_id().to_string()) >=
|
||||
pl.notification_level(mtx::events::state::notification_keys::room);
|
||||
const bool plCheck = pl.user_level(http::client()->user_id().to_string(), create) >=
|
||||
pl.notification_level(mtx::events::state::notification_keys::room);
|
||||
return plCheck;
|
||||
}
|
||||
|
||||
#include "moc_Permissions.cpp"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <mtx/events.hpp>
|
||||
#include <mtx/events/create.hpp>
|
||||
#include <mtx/events/power_levels.hpp>
|
||||
|
||||
class TimelineModel;
|
||||
|
|
@ -13,6 +16,8 @@ class TimelineModel;
|
|||
class Permissions final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Only to be used to refer to C++ values")
|
||||
|
||||
public:
|
||||
Permissions(QString roomId, QObject *parent = nullptr);
|
||||
|
|
@ -28,14 +33,20 @@ public:
|
|||
Q_INVOKABLE int redactLevel();
|
||||
Q_INVOKABLE int changeLevel(int eventType);
|
||||
Q_INVOKABLE int sendLevel(int eventType);
|
||||
Q_INVOKABLE qint64 creatorLevel() const { return mtx::events::state::Creator; }
|
||||
|
||||
Q_INVOKABLE bool canPingRoom();
|
||||
|
||||
void invalidate();
|
||||
|
||||
const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; };
|
||||
const mtx::events::StateEvent<mtx::events::state::Create> &createEvent() const
|
||||
{
|
||||
return create;
|
||||
};
|
||||
|
||||
private:
|
||||
QString roomId_;
|
||||
mtx::events::state::PowerLevels pl;
|
||||
mtx::events::StateEvent<mtx::events::state::Create> create;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -977,6 +977,10 @@ FilteredRoomlistModel::getRoomById(const QString &id) const
|
|||
void
|
||||
FilteredRoomlistModel::updateHiddenTagsAndSpaces()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
||||
beginFilterChange();
|
||||
#endif
|
||||
|
||||
hiddenTags.clear();
|
||||
hiddenSpaces.clear();
|
||||
hideDMs = false;
|
||||
|
|
@ -991,7 +995,11 @@ FilteredRoomlistModel::updateHiddenTagsAndSpaces()
|
|||
hideDMs = true;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
endFilterChange();
|
||||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -212,6 +212,10 @@ public slots:
|
|||
|
||||
void updateFilterTag(QString tagId)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
#endif
|
||||
|
||||
if (tagId.startsWith(QLatin1String("tag:"))) {
|
||||
filterType = FilterBy::Tag;
|
||||
filterStr = tagId.mid(4);
|
||||
|
|
@ -227,7 +231,11 @@ public slots:
|
|||
filterStr.clear();
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
endFilterChange();
|
||||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void updateHiddenTagsAndSpaces();
|
||||
|
|
|
|||
|
|
@ -41,7 +41,13 @@ TimelineFilter::startFiltering()
|
|||
{
|
||||
incrementalSearchIndex = 0;
|
||||
emit isFilteringChanged();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
endFilterChange();
|
||||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
|
||||
|
|
@ -96,6 +102,10 @@ TimelineFilter::setThreadId(const QString &t)
|
|||
{
|
||||
nhlog::ui()->debug("Filtering by thread '{}'", t.toStdString());
|
||||
if (this->threadId != t) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
||||
beginFilterChange();
|
||||
#endif
|
||||
|
||||
this->threadId = t;
|
||||
|
||||
emit threadIdChanged();
|
||||
|
|
@ -104,11 +114,30 @@ TimelineFilter::setThreadId(const QString &t)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineFilter::setFilterNotifications(bool filter)
|
||||
{
|
||||
nhlog::ui()->debug("Filtering by notifications '{}'", filter);
|
||||
if (this->filterByNotifications_ != filter) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
||||
beginFilterChange();
|
||||
#endif
|
||||
this->filterByNotifications_ = filter;
|
||||
|
||||
emit filterNotificationsChanged();
|
||||
startFiltering();
|
||||
fetchMore({});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineFilter::setContentFilter(const QString &c)
|
||||
{
|
||||
nhlog::ui()->debug("Filtering by content '{}'", c.toStdString());
|
||||
if (this->contentFilter != c) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
||||
beginFilterChange();
|
||||
#endif
|
||||
this->contentFilter = c;
|
||||
|
||||
emit contentFilterChanged();
|
||||
|
|
@ -145,7 +174,8 @@ TimelineFilter::sourceDataChanged(const QModelIndex &topLeft,
|
|||
const QModelIndex &bottomRight,
|
||||
const QVector<int> &roles)
|
||||
{
|
||||
if (!roles.contains(TimelineModel::Roles::Body) && !roles.contains(TimelineModel::ThreadId))
|
||||
if (!roles.contains(TimelineModel::Roles::Body) && !roles.contains(TimelineModel::ThreadId) &&
|
||||
!roles.contains(TimelineModel::Notificationlevel))
|
||||
return;
|
||||
|
||||
if (auto s = source()) {
|
||||
|
|
@ -157,6 +187,10 @@ void
|
|||
TimelineFilter::setSource(TimelineModel *s)
|
||||
{
|
||||
if (auto orig = this->source(); orig != s) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
#endif
|
||||
|
||||
cachedCount = 0;
|
||||
incrementalSearchIndex = 0;
|
||||
|
||||
|
|
@ -191,7 +225,12 @@ TimelineFilter::setSource(TimelineModel *s)
|
|||
|
||||
emit sourceChanged();
|
||||
emit isFilteringChanged();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
endFilterChange();
|
||||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,19 +272,27 @@ TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
|
|||
if (source_row > incrementalSearchIndex)
|
||||
return false;
|
||||
|
||||
if (threadId.isEmpty() && contentFilter.isEmpty())
|
||||
if (threadId.isEmpty() && contentFilter.isEmpty() && !filterByNotifications_)
|
||||
return true;
|
||||
|
||||
if (auto s = sourceModel()) {
|
||||
auto idx = s->index(source_row, 0);
|
||||
|
||||
if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body)
|
||||
.toString()
|
||||
.contains(contentFilter, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threadId.isEmpty())
|
||||
if (filterByNotifications_ && s->data(idx, TimelineModel::Notificationlevel)
|
||||
.value<qml_mtx_events::NotificationLevel>() !=
|
||||
qml_mtx_events::NotificationLevel::Highlight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threadId.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return s->data(idx, TimelineModel::EventId) == threadId ||
|
||||
s->data(idx, TimelineModel::ThreadId) == threadId;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class TimelineFilter : public QSortFilterProxyModel
|
|||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged)
|
||||
Q_PROPERTY(bool filterByNotifications READ filterByNotifications WRITE setFilterNotifications
|
||||
NOTIFY filterNotificationsChanged)
|
||||
Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY
|
||||
contentFilterChanged)
|
||||
Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged)
|
||||
|
|
@ -28,12 +30,14 @@ public:
|
|||
explicit TimelineFilter(QObject *parent = nullptr);
|
||||
|
||||
QString filterByThread() const { return threadId; }
|
||||
bool filterByNotifications() const { return filterByNotifications_; }
|
||||
QString filterByContent() const { return contentFilter; }
|
||||
TimelineModel *source() const;
|
||||
int currentIndex() const;
|
||||
bool isFiltering() const;
|
||||
|
||||
void setThreadId(const QString &t);
|
||||
void setFilterNotifications(bool v);
|
||||
void setContentFilter(const QString &t);
|
||||
void setSource(TimelineModel *t);
|
||||
void setCurrentIndex(int idx);
|
||||
|
|
@ -47,6 +51,7 @@ public:
|
|||
|
||||
signals:
|
||||
void threadIdChanged();
|
||||
void filterNotificationsChanged();
|
||||
void contentFilterChanged();
|
||||
void sourceChanged();
|
||||
void currentIndexChanged();
|
||||
|
|
@ -67,4 +72,5 @@ private:
|
|||
|
||||
QString threadId, contentFilter;
|
||||
int cachedCount = 0, incrementalSearchIndex = 0;
|
||||
bool filterByNotifications_ = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -605,8 +605,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
|
|||
case UserName:
|
||||
return QVariant(displayName(QString::fromStdString(acc::sender(event))));
|
||||
case UserPowerlevel: {
|
||||
return static_cast<qlonglong>(
|
||||
permissions_.powerlevelEvent().user_level(acc::sender(event)));
|
||||
return static_cast<qlonglong>(permissions_.powerlevelEvent().user_level(
|
||||
acc::sender(event), permissions_.createEvent()));
|
||||
}
|
||||
|
||||
case Day: {
|
||||
|
|
@ -1137,7 +1137,6 @@ TimelineModel::syncState(const mtx::responses::State &s)
|
|||
avatarChanged = true;
|
||||
nameChanged = true;
|
||||
memberCountChanged = true;
|
||||
|
||||
} else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) {
|
||||
this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
|
||||
emit encryptionChanged();
|
||||
|
|
@ -2422,6 +2421,7 @@ QString
|
|||
TimelineModel::formatPowerLevelEvent(
|
||||
const mtx::events::StateEvent<mtx::events::state::PowerLevels> &event) const
|
||||
{
|
||||
const auto create = permissions_.createEvent();
|
||||
mtx::events::StateEvent<mtx::events::state::PowerLevels> const *prevEvent = nullptr;
|
||||
if (!event.unsigned_data.replaces_state.empty()) {
|
||||
auto tempPrevEvent = events.get(event.unsigned_data.replaces_state, event.event_id);
|
||||
|
|
@ -2441,15 +2441,15 @@ TimelineModel::formatPowerLevelEvent(
|
|||
if (!prevEvent)
|
||||
return tr("%1 has changed the room's permissions.").arg(sender_name);
|
||||
|
||||
auto calc_affected = [&event,
|
||||
&prevEvent](int64_t newPowerlevelSetting) -> std::pair<QStringList, int> {
|
||||
auto calc_affected =
|
||||
[&event, &prevEvent, &create](int64_t newPowerlevelSetting) -> std::pair<QStringList, int> {
|
||||
QStringList affected{};
|
||||
auto numberOfAffected = 0;
|
||||
// We do only compare to people with explicit PL. Usually others are not going to be
|
||||
// affected either way and this is cheaper to iterate over.
|
||||
for (auto const &[mxid, currentPowerlevel] : event.content.users) {
|
||||
if (currentPowerlevel == newPowerlevelSetting &&
|
||||
prevEvent->content.user_level(mxid) < newPowerlevelSetting) {
|
||||
prevEvent->content.user_level(mxid, create) < newPowerlevelSetting) {
|
||||
numberOfAffected++;
|
||||
if (numberOfAffected <= 2) {
|
||||
affected.push_back(QString::fromStdString(mxid));
|
||||
|
|
@ -2626,24 +2626,25 @@ TimelineModel::formatPowerLevelEvent(
|
|||
// Compare if a Powerlevel of a user changed
|
||||
for (auto const &[mxid, powerlevel] : event.content.users) {
|
||||
auto nameOfChangedUser = utils::replaceEmoji(displayName(QString::fromStdString(mxid)));
|
||||
if (prevEvent->content.user_level(mxid) != powerlevel) {
|
||||
if (prevEvent->content.user_level(mxid, create) != powerlevel) {
|
||||
if (powerlevel >= administrator_power_level) {
|
||||
resultingMessage.append(tr("%1 has made %2 an administrator of this room.")
|
||||
.arg(sender_name, nameOfChangedUser));
|
||||
} else if (powerlevel >= moderator_power_level &&
|
||||
powerlevel > prevEvent->content.user_level(mxid)) {
|
||||
powerlevel > prevEvent->content.user_level(mxid, create)) {
|
||||
resultingMessage.append(tr("%1 has made %2 a moderator of this room.")
|
||||
.arg(sender_name, nameOfChangedUser));
|
||||
} else if (powerlevel >= moderator_power_level &&
|
||||
powerlevel < prevEvent->content.user_level(mxid)) {
|
||||
powerlevel < prevEvent->content.user_level(mxid, create)) {
|
||||
resultingMessage.append(tr("%1 has downgraded %2 to moderator of this room.")
|
||||
.arg(sender_name, nameOfChangedUser));
|
||||
} else {
|
||||
resultingMessage.append(tr("%1 has changed the powerlevel of %2 from %3 to %4.")
|
||||
.arg(sender_name,
|
||||
nameOfChangedUser,
|
||||
QString::number(prevEvent->content.user_level(mxid)),
|
||||
QString::number(powerlevel)));
|
||||
resultingMessage.append(
|
||||
tr("%1 has changed the powerlevel of %2 from %3 to %4.")
|
||||
.arg(sender_name,
|
||||
nameOfChangedUser,
|
||||
QString::number(prevEvent->content.user_level(mxid, create)),
|
||||
QString::number(powerlevel)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3374,6 +3375,7 @@ TimelineModel::pushrulesRoomContext() const
|
|||
cache::displayName(room_id_.toStdString(), http::client()->user_id().to_string()),
|
||||
.member_count = cache::client()->memberCount(room_id_.toStdString()),
|
||||
.power_levels = permissions_.powerlevelEvent(),
|
||||
.create = permissions_.createEvent(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ public:
|
|||
Q_INVOKABLE void openUserProfile(QString userid);
|
||||
Q_INVOKABLE void unpin(const QString &id);
|
||||
Q_INVOKABLE void pin(const QString &id);
|
||||
Q_INVOKABLE void markEventAsRead(const QString &id) { this->readEvent(id.toStdString()); }
|
||||
Q_INVOKABLE void showReadReceipts(const QString &id);
|
||||
Q_INVOKABLE void redactEvent(const QString &id, const QString &reason = "");
|
||||
Q_INVOKABLE void redactAllFromUser(const QString &userid, const QString &reason = "");
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ EventExpiry::load()
|
|||
if (auto temp = cache::client()->getAccountData(mtx::events::EventType::NhekoEventExpiry,
|
||||
roomid_.toStdString())) {
|
||||
auto h = std::get<mtx::events::AccountDataEvent<
|
||||
mtx::events::account_data::nheko_extensions::EventExpiry>>(*temp);
|
||||
mtx::events::account_data::nheko_extensions::EventExpiry>>(*temp);
|
||||
this->event = std::move(h.content);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,9 +61,7 @@ getFrameRate(const GValue *value)
|
|||
void
|
||||
addFrameRate(std::vector<std::string> &rates, const FrameRate &rate)
|
||||
{
|
||||
constexpr double minimumFrameRate = 15.0;
|
||||
if (static_cast<double>(rate.first) / rate.second >= minimumFrameRate)
|
||||
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
|
||||
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
11
third_party/olm-patches/0002-fix-cmake-cmp0148.patch
vendored
Normal file
11
third_party/olm-patches/0002-fix-cmake-cmp0148.patch
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
--- CMakeLists.txt.orig 2026-01-25 20:50:49.905592647 -0500
|
||||
+++ CMakeLists.txt 2026-01-25 20:50:49.908925942 -0500
|
||||
@@ -1,4 +1,7 @@
|
||||
-cmake_minimum_required(VERSION 3.4)
|
||||
+cmake_minimum_required(VERSION 3.5)
|
||||
+if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.27")
|
||||
+ cmake_policy(SET CMP0148 OLD)
|
||||
+endif()
|
||||
|
||||
project(olm VERSION 3.2.16 LANGUAGES CXX C)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue