Compare commits
91 Commits
fix-editin
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbceaadbba | ||
|
|
61f3d6c9ae | ||
|
|
d8b2436f0d | ||
|
|
ea76edce74 | ||
|
|
5482aad7ba | ||
|
|
aaa26571d1 | ||
|
|
554e3576fd | ||
|
|
8d2c7ff8c2 | ||
|
|
92261eb94f | ||
|
|
4dfb7fc3e2 | ||
|
|
2728446692 | ||
|
|
dfb32fe687 | ||
|
|
55507112a9 | ||
|
|
d9aa77a9f4 | ||
|
|
c0ea52c331 | ||
|
|
f1b9d2329f | ||
|
|
0707fd8189 | ||
|
|
6bef2205db | ||
|
|
ace62b4df1 | ||
|
|
d76e512785 | ||
|
|
db2692c411 | ||
|
|
4c9bdd03ce | ||
|
|
efa2c012a7 | ||
|
|
072dc1b6a3 | ||
|
|
4066427168 | ||
|
|
b901c1bdf2 | ||
|
|
532310d739 | ||
|
|
4f7d32df2b | ||
|
|
833d2159e7 | ||
|
|
ccf8701395 | ||
|
|
5d696aabc4 | ||
|
|
ebd521e2ee | ||
|
|
9d5303113e | ||
|
|
3850fe15a5 | ||
|
|
5392481d06 | ||
|
|
0ce048517b | ||
|
|
c51e16e16c | ||
|
|
765a95050d | ||
|
|
38dcefffcb | ||
|
|
391b61dcb4 | ||
|
|
d76c9fcbcf | ||
|
|
f7b8ae2af2 | ||
|
|
ddbf9b60d4 | ||
|
|
8948ff5faa | ||
|
|
d270e202a8 | ||
|
|
61e1dd14ba | ||
|
|
eee93e0f1f | ||
|
|
826760a55c | ||
|
|
749398df8f | ||
|
|
150968d226 | ||
|
|
d81df24e7c | ||
|
|
594a5cf6ca | ||
|
|
0af420b824 | ||
|
|
31fed8362a | ||
|
|
dbcf8c6327 | ||
|
|
8012392400 | ||
|
|
3e48578b44 | ||
|
|
d7e656e57f | ||
|
|
8e83b923d9 | ||
|
|
a739f0f09f | ||
|
|
bc74737b0f | ||
|
|
e92fccf904 | ||
|
|
20cbedfff5 | ||
|
|
e0d508d3dd | ||
|
|
af318a2bae | ||
|
|
6b4e81c763 | ||
|
|
dcac63aa04 | ||
|
|
7818747e45 | ||
|
|
7cb1f856ca | ||
|
|
5955c8e7dc | ||
|
|
dee3c279e8 | ||
|
|
94427280d8 | ||
|
|
8b245b1cc9 | ||
|
|
0513cd10c4 | ||
|
|
28b5631d06 | ||
|
|
5f2cd92da7 | ||
|
|
4535125c54 | ||
|
|
8831da956a | ||
|
|
f2ec6e1d4c | ||
|
|
93f7def532 | ||
|
|
782f5517d3 | ||
|
|
f238c18ce8 | ||
|
|
4fe0ea373f | ||
|
|
982d21dd58 | ||
|
|
7002132bde | ||
|
|
1ef931f7e7 | ||
|
|
8797015c6a | ||
|
|
85a562d469 | ||
|
|
f50c62ba12 | ||
|
|
13f05a0995 | ||
|
|
1adddcc0d9 |
@@ -2,7 +2,7 @@
|
|||||||
"id": "org.kde.neochat",
|
"id": "org.kde.neochat",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"runtime": "org.kde.Platform",
|
"runtime": "org.kde.Platform",
|
||||||
"runtime-version": "5.15-21.08",
|
"runtime-version": "5.15-22.08",
|
||||||
"sdk": "org.kde.Sdk",
|
"sdk": "org.kde.Sdk",
|
||||||
"command": "neochat",
|
"command": "neochat",
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ compile_commands.json
|
|||||||
kate.project.ctags.*
|
kate.project.ctags.*
|
||||||
*.user
|
*.user
|
||||||
.flatpak-builder/
|
.flatpak-builder/
|
||||||
|
.idea/
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
include:
|
include:
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||||
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||||
# TODO enable once we can have qt6 libQuotient on the CI
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||||
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||||
|
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
|
||||||
|
|||||||
@@ -30,4 +30,4 @@ Dependencies:
|
|||||||
'frameworks/kdbusaddons': '@stable'
|
'frameworks/kdbusaddons': '@stable'
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
|
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows' ]
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(NeoChat)
|
# KDE Applications version, managed by release script.
|
||||||
set(PROJECT_VERSION "22.11")
|
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||||
|
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
||||||
|
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||||
|
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||||
|
|
||||||
|
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||||
|
|
||||||
set(KF5_MIN_VERSION "5.91.0")
|
set(KF5_MIN_VERSION "5.91.0")
|
||||||
set(QT_MIN_VERSION "5.15.2")
|
set(QT_MIN_VERSION "5.15.2")
|
||||||
|
if (ANDROID)
|
||||||
|
set(QT_MIN_VERSION "5.15.8")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||||
|
|
||||||
@@ -122,6 +130,8 @@ set_package_properties(KF5DocTools PROPERTIES DESCRIPTION
|
|||||||
TYPE OPTIONAL
|
TYPE OPTIONAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(Sqlite3)
|
||||||
|
|
||||||
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
||||||
cmake_policy(SET CMP0063 OLD)
|
cmake_policy(SET CMP0063 OLD)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
0
LICENSES/MIT.txt
Executable file → Normal file
0
LICENSES/MIT.txt
Executable file → Normal file
@@ -14,7 +14,8 @@
|
|||||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||||
android:label="NeoChat"
|
android:label="NeoChat"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
|
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
|
||||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
|
||||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||||
@@ -38,8 +38,6 @@
|
|||||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||||
<!-- Messages maps -->
|
<!-- Messages maps -->
|
||||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
|
||||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
|
||||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||||
|
|
||||||
<!-- Splash screen -->
|
<!-- Splash screen -->
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.6.4'
|
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +73,10 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion qtMinSdkVersion
|
minSdkVersion qtMinSdkVersion
|
||||||
targetSdkVersion qtTargetSdkVersion
|
targetSdkVersion qtTargetSdkVersion
|
||||||
|
applicationId "org.kde.neochat"
|
||||||
|
namespace "org.kde.neochat"
|
||||||
|
versionCode timestamp
|
||||||
|
versionName projectVersionFull
|
||||||
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
|
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,20 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="23.01" date="2023-01-30">
|
||||||
|
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||||
|
<description>
|
||||||
|
<p>New features and bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Notifications will now be shown for all accounts, not just the active one</li>
|
||||||
|
<li>There is a new "compact" mode for the room list</li>
|
||||||
|
<li>You can now search in the room history</li>
|
||||||
|
<li>Emojis and Reactions have been significantly improved</li>
|
||||||
|
<li>Fixed several crashes around user invitations</li>
|
||||||
|
<li>Room permission settings can now be configured</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="22.11" date="2022-11-30">
|
<release version="22.11" date="2022-11-30">
|
||||||
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
|
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
1681
po/ar/neochat.po
1681
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1841
po/az/neochat.po
1841
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1639
po/ca/neochat.po
1639
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1694
po/cs/neochat.po
1694
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1664
po/da/neochat.po
1664
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1699
po/de/neochat.po
1699
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
2432
po/el/neochat.po
2432
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1691
po/en_GB/neochat.po
1691
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1665
po/es/neochat.po
1665
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1649
po/eu/neochat.po
1649
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1835
po/fi/neochat.po
1835
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1707
po/fr/neochat.po
1707
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1839
po/hu/neochat.po
1839
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
2079
po/ia/neochat.po
2079
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1808
po/id/neochat.po
1808
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1717
po/ie/neochat.po
1717
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1723
po/it/neochat.po
1723
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1613
po/ja/neochat.po
1613
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1654
po/ka/neochat.po
1654
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1818
po/ko/neochat.po
1818
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1653
po/nl/neochat.po
1653
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1482
po/nn/neochat.po
1482
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1813
po/pa/neochat.po
1813
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1771
po/pl/neochat.po
1771
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1703
po/pt/neochat.po
1703
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1842
po/pt_BR/neochat.po
1842
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1709
po/ru/neochat.po
1709
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1851
po/sk/neochat.po
1851
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1678
po/sl/neochat.po
1678
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1823
po/sv/neochat.po
1823
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1686
po/ta/neochat.po
1686
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1675
po/tok/neochat.po
1675
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1678
po/tr/neochat.po
1678
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1667
po/uk/neochat.po
1667
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1651
po/zh_CN/neochat.po
1651
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1610
po/zh_TW/neochat.po
1610
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -6,47 +6,47 @@
|
|||||||
add_library(neochat STATIC
|
add_library(neochat STATIC
|
||||||
controller.cpp
|
controller.cpp
|
||||||
actionshandler.cpp
|
actionshandler.cpp
|
||||||
emojimodel.cpp
|
models/emojimodel.cpp
|
||||||
emojitones.cpp
|
emojitones.cpp
|
||||||
customemojimodel.cpp
|
models/customemojimodel.cpp
|
||||||
clipboard.cpp
|
clipboard.cpp
|
||||||
matriximageprovider.cpp
|
matriximageprovider.cpp
|
||||||
messageeventmodel.cpp
|
models/messageeventmodel.cpp
|
||||||
messagefiltermodel.cpp
|
models/messagefiltermodel.cpp
|
||||||
roomlistmodel.cpp
|
models/roomlistmodel.cpp
|
||||||
sortfilterspacelistmodel.cpp
|
models/sortfilterspacelistmodel.cpp
|
||||||
spacehierarchycache.cpp
|
spacehierarchycache.cpp
|
||||||
roommanager.cpp
|
roommanager.cpp
|
||||||
neochatroom.cpp
|
neochatroom.cpp
|
||||||
neochatuser.cpp
|
neochatuser.cpp
|
||||||
userlistmodel.cpp
|
models/userlistmodel.cpp
|
||||||
userfiltermodel.cpp
|
models/userfiltermodel.cpp
|
||||||
publicroomlistmodel.cpp
|
models/publicroomlistmodel.cpp
|
||||||
userdirectorylistmodel.cpp
|
models/userdirectorylistmodel.cpp
|
||||||
keywordnotificationrulemodel.cpp
|
models/keywordnotificationrulemodel.cpp
|
||||||
utils.cpp
|
utils.cpp
|
||||||
notificationsmanager.cpp
|
notificationsmanager.cpp
|
||||||
sortfilterroomlistmodel.cpp
|
models/sortfilterroomlistmodel.cpp
|
||||||
chatdocumenthandler.cpp
|
chatdocumenthandler.cpp
|
||||||
devicesmodel.cpp
|
models/devicesmodel.cpp
|
||||||
filetypesingleton.cpp
|
filetypesingleton.cpp
|
||||||
login.cpp
|
login.cpp
|
||||||
stickerevent.cpp
|
stickerevent.cpp
|
||||||
webshortcutmodel.cpp
|
models/webshortcutmodel.cpp
|
||||||
blurhash.cpp
|
blurhash.cpp
|
||||||
blurhashimageprovider.cpp
|
blurhashimageprovider.cpp
|
||||||
joinrulesevent.cpp
|
joinrulesevent.cpp
|
||||||
collapsestateproxymodel.cpp
|
models/collapsestateproxymodel.cpp
|
||||||
urlhelper.cpp
|
urlhelper.cpp
|
||||||
windowcontroller.cpp
|
windowcontroller.cpp
|
||||||
linkpreviewer.cpp
|
linkpreviewer.cpp
|
||||||
completionmodel.cpp
|
models/completionmodel.cpp
|
||||||
completionproxymodel.cpp
|
models/completionproxymodel.cpp
|
||||||
actionsmodel.cpp
|
models/actionsmodel.cpp
|
||||||
serverlistmodel.cpp
|
models/serverlistmodel.cpp
|
||||||
statemodel.cpp
|
models/statemodel.cpp
|
||||||
filetransferpseudojob.cpp
|
filetransferpseudojob.cpp
|
||||||
searchmodel.cpp
|
models/searchmodel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(neochat-app
|
add_executable(neochat-app
|
||||||
@@ -103,6 +103,9 @@ endif()
|
|||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
target_sources(neochat PRIVATE notifyrc.qrc)
|
target_sources(neochat PRIVATE notifyrc.qrc)
|
||||||
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
|
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
|
||||||
|
if(SQLite3_FOUND)
|
||||||
|
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
|
||||||
|
endif()
|
||||||
target_sources(neochat-app PRIVATE notifyrc.qrc)
|
target_sources(neochat-app PRIVATE notifyrc.qrc)
|
||||||
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
|
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
|
||||||
kirigami_package_breeze_icons(ICONS
|
kirigami_package_breeze_icons(ICONS
|
||||||
@@ -159,7 +162,7 @@ if(ANDROID)
|
|||||||
"zoom-out"
|
"zoom-out"
|
||||||
"image-rotate-left-symbolic"
|
"image-rotate-left-symbolic"
|
||||||
"image-rotate-right-symbolic"
|
"image-rotate-right-symbolic"
|
||||||
"channel-insecure-symbolic"
|
"channel-secure-symbolic"
|
||||||
"download"
|
"download"
|
||||||
"smiley"
|
"smiley"
|
||||||
"tools-check-spelling"
|
"tools-check-spelling"
|
||||||
|
|||||||
@@ -13,10 +13,11 @@
|
|||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
|
|
||||||
#include "actionsmodel.h"
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "customemojimodel.h"
|
#include "models/actionsmodel.h"
|
||||||
|
#include "models/customemojimodel.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
|
||||||
@@ -58,9 +59,9 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
|
|||||||
Q_EMIT roomChanged();
|
Q_EMIT roomChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionsHandler::handleMessage()
|
void ActionsHandler::handleNewMessage()
|
||||||
{
|
{
|
||||||
checkEffects();
|
checkEffects(m_room->chatBoxText());
|
||||||
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
|
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
|
||||||
QUrl url(m_room->chatBoxAttachmentPath());
|
QUrl url(m_room->chatBoxAttachmentPath());
|
||||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||||
@@ -69,13 +70,39 @@ void ActionsHandler::handleMessage()
|
|||||||
m_room->setChatBoxText({});
|
m_room->setChatBoxText({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QString handledText = m_room->chatBoxText();
|
|
||||||
|
|
||||||
std::sort(m_room->mentions()->begin(), m_room->mentions()->end(), [](const auto &a, const auto &b) -> bool {
|
QString handledText = m_room->chatBoxText();
|
||||||
|
handledText = handleMentions(handledText);
|
||||||
|
handleMessage(m_room->chatBoxText(), handledText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionsHandler::handleEdit()
|
||||||
|
{
|
||||||
|
checkEffects(m_room->editText());
|
||||||
|
|
||||||
|
QString handledText = m_room->editText();
|
||||||
|
handledText = handleMentions(handledText, true);
|
||||||
|
handleMessage(m_room->editText(), handledText, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
|
||||||
|
{
|
||||||
|
if (!m_room) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<Mention> *mentions;
|
||||||
|
if (isEdit) {
|
||||||
|
mentions = m_room->editMentions();
|
||||||
|
} else {
|
||||||
|
mentions = m_room->mentions();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
|
||||||
return a.cursor.anchor() > b.cursor.anchor();
|
return a.cursor.anchor() > b.cursor.anchor();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const auto &mention : *m_room->mentions()) {
|
for (const auto &mention : *mentions) {
|
||||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -83,11 +110,16 @@ void ActionsHandler::handleMessage()
|
|||||||
mention.cursor.position() - mention.cursor.anchor(),
|
mention.cursor.position() - mention.cursor.anchor(),
|
||||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
|
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
|
||||||
}
|
}
|
||||||
m_room->mentions()->clear();
|
mentions->clear();
|
||||||
|
|
||||||
|
return handledText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
|
||||||
|
{
|
||||||
if (NeoChatConfig::allowQuickEdit()) {
|
if (NeoChatConfig::allowQuickEdit()) {
|
||||||
QRegularExpression sed("^s/([^/]*)/([^/]*)(/g)?$");
|
QRegularExpression sed("^s/([^/]*)/([^/]*)(/g)?$");
|
||||||
auto match = sed.match(m_room->chatBoxText());
|
auto match = sed.match(text);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
const QString regex = match.captured(1);
|
const QString regex = match.captured(1);
|
||||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||||
@@ -146,13 +178,13 @@ void ActionsHandler::handleMessage()
|
|||||||
if (handledText.length() == 0) {
|
if (handledText.length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_room->postMessage(m_room->chatBoxText(), handledText, messageType, m_room->chatBoxReplyId(), m_room->chatBoxEditId());
|
|
||||||
|
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionsHandler::checkEffects()
|
void ActionsHandler::checkEffects(const QString &text)
|
||||||
{
|
{
|
||||||
std::optional<QString> effect = std::nullopt;
|
std::optional<QString> effect = std::nullopt;
|
||||||
const auto &text = m_room->chatBoxText();
|
|
||||||
if (text.contains("\u2744")) {
|
if (text.contains("\u2744")) {
|
||||||
effect = QLatin1String("snowflake");
|
effect = QLatin1String("snowflake");
|
||||||
} else if (text.contains("\u1F386")) {
|
} else if (text.contains("\u1F386")) {
|
||||||
|
|||||||
@@ -33,14 +33,22 @@ Q_SIGNALS:
|
|||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
|
||||||
/// \brief Post a message.
|
/**
|
||||||
///
|
* @brief Pre-process text and send message.
|
||||||
/// This also interprets commands if any.
|
*/
|
||||||
void handleMessage();
|
void handleNewMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pre-process text and send edit.
|
||||||
|
*/
|
||||||
|
void handleEdit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NeoChatRoom *m_room = nullptr;
|
NeoChatRoom *m_room = nullptr;
|
||||||
void checkEffects();
|
void checkEffects(const QString &text);
|
||||||
|
|
||||||
|
QString handleMentions(QString handledText, const bool &isEdit = false);
|
||||||
|
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
QString markdownToHTML(const QString &markdown);
|
QString markdownToHTML(const QString &markdown);
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#include <Sonnet/BackgroundChecker>
|
#include <Sonnet/BackgroundChecker>
|
||||||
#include <Sonnet/Settings>
|
#include <Sonnet/Settings>
|
||||||
|
|
||||||
#include "actionsmodel.h"
|
#include "models/actionsmodel.h"
|
||||||
|
#include "models/roomlistmodel.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roomlistmodel.h"
|
|
||||||
|
|
||||||
class SyntaxHighlighter : public QSyntaxHighlighter
|
class SyntaxHighlighter : public QSyntaxHighlighter
|
||||||
{
|
{
|
||||||
@@ -105,14 +105,19 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
|||||||
{
|
{
|
||||||
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
|
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
|
||||||
m_completionModel->setRoom(m_room);
|
m_completionModel->setRoom(m_room);
|
||||||
static NeoChatRoom *previousRoom = nullptr;
|
static QPointer<NeoChatRoom> previousRoom = nullptr;
|
||||||
if (previousRoom) {
|
if (previousRoom) {
|
||||||
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
|
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
|
||||||
|
disconnect(previousRoom, &NeoChatRoom::editTextChanged, this, nullptr);
|
||||||
}
|
}
|
||||||
previousRoom = m_room;
|
previousRoom = m_room;
|
||||||
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
|
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
|
||||||
int start = completionStartIndex();
|
int start = completionStartIndex();
|
||||||
m_completionModel->setText(m_room->chatBoxText().mid(start, cursorPosition() - start), m_room->chatBoxText().mid(start));
|
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||||
|
});
|
||||||
|
connect(m_room, &NeoChatRoom::editTextChanged, this, [this]() {
|
||||||
|
int start = completionStartIndex();
|
||||||
|
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
|
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
|
||||||
@@ -123,7 +128,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int start = completionStartIndex();
|
int start = completionStartIndex();
|
||||||
m_completionModel->setText(m_room->chatBoxText().mid(start, cursorPosition() - start), m_room->chatBoxText().mid(start));
|
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +143,7 @@ int ChatDocumentHandler::completionStartIndex() const
|
|||||||
#else
|
#else
|
||||||
const auto cursor = cursorPosition();
|
const auto cursor = cursorPosition();
|
||||||
#endif
|
#endif
|
||||||
const auto &text = m_room->chatBoxText();
|
const auto &text = getText();
|
||||||
auto start = std::min(cursor, text.size()) - 1;
|
auto start = std::min(cursor, text.size()) - 1;
|
||||||
while (start > -1) {
|
while (start > -1) {
|
||||||
if (text.at(start) == QLatin1Char(' ')) {
|
if (text.at(start) == QLatin1Char(' ')) {
|
||||||
@@ -150,6 +155,20 @@ int ChatDocumentHandler::completionStartIndex() const
|
|||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatDocumentHandler::isEdit() const
|
||||||
|
{
|
||||||
|
return m_isEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDocumentHandler::setIsEdit(bool edit)
|
||||||
|
{
|
||||||
|
if (edit == m_isEdit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_isEdit = edit;
|
||||||
|
Q_EMIT isEditChanged();
|
||||||
|
}
|
||||||
|
|
||||||
QQuickTextDocument *ChatDocumentHandler::document() const
|
QQuickTextDocument *ChatDocumentHandler::document() const
|
||||||
{
|
{
|
||||||
return m_document;
|
return m_document;
|
||||||
@@ -204,7 +223,7 @@ void ChatDocumentHandler::complete(int index)
|
|||||||
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
|
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
|
||||||
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Text).toString();
|
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Text).toString();
|
||||||
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
|
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
|
||||||
auto text = m_room->chatBoxText();
|
auto text = getText();
|
||||||
auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
|
auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
|
||||||
QTextCursor cursor(document()->textDocument());
|
QTextCursor cursor(document()->textDocument());
|
||||||
cursor.setPosition(at);
|
cursor.setPosition(at);
|
||||||
@@ -213,11 +232,11 @@ void ChatDocumentHandler::complete(int index)
|
|||||||
cursor.setPosition(at);
|
cursor.setPosition(at);
|
||||||
cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor);
|
cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor);
|
||||||
cursor.setKeepPositionOnInsert(true);
|
cursor.setKeepPositionOnInsert(true);
|
||||||
m_room->mentions()->push_back({cursor, name, 0, 0, id});
|
pushMention({cursor, name, 0, 0, id});
|
||||||
m_highlighter->rehighlight();
|
m_highlighter->rehighlight();
|
||||||
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) {
|
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) {
|
||||||
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
|
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
|
||||||
auto text = m_room->chatBoxText();
|
auto text = getText();
|
||||||
auto at = text.lastIndexOf(QLatin1Char('/'));
|
auto at = text.lastIndexOf(QLatin1Char('/'));
|
||||||
QTextCursor cursor(document()->textDocument());
|
QTextCursor cursor(document()->textDocument());
|
||||||
cursor.setPosition(at);
|
cursor.setPosition(at);
|
||||||
@@ -225,7 +244,7 @@ void ChatDocumentHandler::complete(int index)
|
|||||||
cursor.insertText(QStringLiteral("/%1 ").arg(command));
|
cursor.insertText(QStringLiteral("/%1 ").arg(command));
|
||||||
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) {
|
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) {
|
||||||
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
|
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
|
||||||
auto text = m_room->chatBoxText();
|
auto text = getText();
|
||||||
auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
|
auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
|
||||||
QTextCursor cursor(document()->textDocument());
|
QTextCursor cursor(document()->textDocument());
|
||||||
cursor.setPosition(at);
|
cursor.setPosition(at);
|
||||||
@@ -234,11 +253,11 @@ void ChatDocumentHandler::complete(int index)
|
|||||||
cursor.setPosition(at);
|
cursor.setPosition(at);
|
||||||
cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor);
|
cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor);
|
||||||
cursor.setKeepPositionOnInsert(true);
|
cursor.setKeepPositionOnInsert(true);
|
||||||
m_room->mentions()->push_back({cursor, alias, 0, 0, alias});
|
pushMention({cursor, alias, 0, 0, alias});
|
||||||
m_highlighter->rehighlight();
|
m_highlighter->rehighlight();
|
||||||
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) {
|
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) {
|
||||||
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
|
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
|
||||||
auto text = m_room->chatBoxText();
|
auto text = getText();
|
||||||
auto at = text.lastIndexOf(QLatin1Char(':'));
|
auto at = text.lastIndexOf(QLatin1Char(':'));
|
||||||
QTextCursor cursor(document()->textDocument());
|
QTextCursor cursor(document()->textDocument());
|
||||||
cursor.setPosition(at);
|
cursor.setPosition(at);
|
||||||
@@ -281,3 +300,27 @@ void ChatDocumentHandler::setSelectionEnd(int position)
|
|||||||
m_selectionEnd = position;
|
m_selectionEnd = position;
|
||||||
Q_EMIT selectionEndChanged();
|
Q_EMIT selectionEndChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChatDocumentHandler::getText() const
|
||||||
|
{
|
||||||
|
if (!m_room) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
if (m_isEdit) {
|
||||||
|
return m_room->editText();
|
||||||
|
} else {
|
||||||
|
return m_room->chatBoxText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||||
|
{
|
||||||
|
if (!m_room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_isEdit) {
|
||||||
|
m_room->editMentions()->push_back(mention);
|
||||||
|
} else {
|
||||||
|
m_room->mentions()->push_back(mention);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
#include <QQuickTextDocument>
|
#include <QQuickTextDocument>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
#include "completionmodel.h"
|
#include "models/completionmodel.h"
|
||||||
#include "userlistmodel.h"
|
#include "models/userlistmodel.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
class QTextDocument;
|
class QTextDocument;
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
@@ -17,6 +18,14 @@ class SyntaxHighlighter;
|
|||||||
class ChatDocumentHandler : public QObject
|
class ChatDocumentHandler : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Is the instance being used to handle an edit message.
|
||||||
|
*
|
||||||
|
* This is needed to ensure that the text and mentions are saved and retrieved
|
||||||
|
* from the correct parameters in the assigned room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
|
||||||
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
|
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
|
||||||
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
|
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
|
||||||
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
|
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
|
||||||
@@ -24,11 +33,14 @@ class ChatDocumentHandler : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
||||||
|
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room NOTIFY roomChanged)
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] bool isEdit() const;
|
||||||
|
void setIsEdit(bool edit);
|
||||||
|
|
||||||
[[nodiscard]] QQuickTextDocument *document() const;
|
[[nodiscard]] QQuickTextDocument *document() const;
|
||||||
void setDocument(QQuickTextDocument *document);
|
void setDocument(QQuickTextDocument *document);
|
||||||
|
|
||||||
@@ -49,6 +61,7 @@ public:
|
|||||||
void updateCompletions();
|
void updateCompletions();
|
||||||
CompletionModel *completionModel() const;
|
CompletionModel *completionModel() const;
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
void isEditChanged();
|
||||||
void documentChanged();
|
void documentChanged();
|
||||||
void cursorPositionChanged();
|
void cursorPositionChanged();
|
||||||
void roomChanged();
|
void roomChanged();
|
||||||
@@ -59,6 +72,8 @@ Q_SIGNALS:
|
|||||||
private:
|
private:
|
||||||
int completionStartIndex() const;
|
int completionStartIndex() const;
|
||||||
|
|
||||||
|
bool m_isEdit;
|
||||||
|
|
||||||
QQuickTextDocument *m_document;
|
QQuickTextDocument *m_document;
|
||||||
|
|
||||||
NeoChatRoom *m_room = nullptr;
|
NeoChatRoom *m_room = nullptr;
|
||||||
@@ -68,6 +83,9 @@ private:
|
|||||||
int m_selectionStart;
|
int m_selectionStart;
|
||||||
int m_selectionEnd;
|
int m_selectionEnd;
|
||||||
|
|
||||||
|
QString getText() const;
|
||||||
|
void pushMention(const Mention mention) const;
|
||||||
|
|
||||||
SyntaxHighlighter *m_highlighter = nullptr;
|
SyntaxHighlighter *m_highlighter = nullptr;
|
||||||
|
|
||||||
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;
|
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;
|
||||||
|
|||||||
@@ -33,13 +33,14 @@ QImage Clipboard::image() const
|
|||||||
|
|
||||||
QString Clipboard::saveImage(QString localPath) const
|
QString Clipboard::saveImage(QString localPath) const
|
||||||
{
|
{
|
||||||
if (!QDir().exists(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)))) {
|
QString imageDir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
|
||||||
QDir().mkdir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
|
|
||||||
|
if (!QDir().exists(imageDir)) {
|
||||||
|
QDir().mkdir(imageDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localPath.isEmpty()) {
|
if (localPath.isEmpty()) {
|
||||||
localPath = QStringLiteral("file://%1/screenshots/%2.png")
|
localPath = QStringLiteral("file://%1/%2.png").arg(imageDir, QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
|
||||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
|
|
||||||
QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
|
|
||||||
}
|
}
|
||||||
QUrl url(localPath);
|
QUrl url(localPath);
|
||||||
if (!url.isLocalFile()) {
|
if (!url.isLocalFile()) {
|
||||||
@@ -51,14 +52,11 @@ QString Clipboard::saveImage(QString localPath) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir dir;
|
if (image.save(url.toLocalFile())) {
|
||||||
if (!dir.exists(localPath)) {
|
|
||||||
dir.mkpath(localPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
image.save(url.toLocalFile());
|
|
||||||
|
|
||||||
return localPath;
|
return localPath;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Clipboard::saveText(QString message)
|
void Clipboard::saveText(QString message)
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "collapsestateproxymodel.h"
|
|
||||||
|
|
||||||
#include <KLocalizedString>
|
|
||||||
|
|
||||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(source_parent);
|
|
||||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
|
||||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
|
||||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool() // If it's a new day, show it
|
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventResolvedTypeRole)
|
|
||||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0),
|
|
||||||
MessageEventModel::EventResolvedTypeRole) // Also show it if it's of a different type than the one before TODO improve in
|
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::AuthorIdRole)
|
|
||||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::AuthorIdRole); // Also show it if it's a different author
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (role == AggregateDisplayRole) {
|
|
||||||
return aggregateEventToString(mapToSource(index).row());
|
|
||||||
}
|
|
||||||
return sourceModel()->data(mapToSource(index), role);
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
|
||||||
{
|
|
||||||
auto roles = sourceModel()->roleNames();
|
|
||||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
|
||||||
{
|
|
||||||
QStringList parts;
|
|
||||||
for (int i = sourceRow; i >= 0; i--) {
|
|
||||||
parts += sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString();
|
|
||||||
if (sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventTypeRole)
|
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
|
||||||
|| (i > 0
|
|
||||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventResolvedTypeRole)
|
|
||||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventResolvedTypeRole)) // or of a different type
|
|
||||||
|| (i > 0
|
|
||||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorIdRole)
|
|
||||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::AuthorIdRole)) // or by a different author
|
|
||||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!parts.isEmpty()) {
|
|
||||||
QStringList chunks;
|
|
||||||
while (!parts.isEmpty()) {
|
|
||||||
chunks += QString();
|
|
||||||
int count = 1;
|
|
||||||
auto part = parts.takeFirst();
|
|
||||||
chunks.last() += part;
|
|
||||||
while (!parts.isEmpty() && parts.first() == part) {
|
|
||||||
parts.removeFirst();
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
if (count > 1) {
|
|
||||||
chunks.last() += i18ncp("[user did something] n times", " %1 time", " %1 times", count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QString text = chunks.takeFirst();
|
|
||||||
|
|
||||||
if (chunks.size() > 0) {
|
|
||||||
while (chunks.size() > 1) {
|
|
||||||
text += i18nc("[action 1], [action 2 and action 3]", ", ");
|
|
||||||
text += chunks.takeFirst();
|
|
||||||
}
|
|
||||||
text += i18nc("[action 1, action 2] and [action 3]", " and ");
|
|
||||||
text += chunks.takeFirst();
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,11 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
#include <qt5keychain/keychain.h>
|
#include <qt5keychain/keychain.h>
|
||||||
|
#else
|
||||||
|
#include <qt6keychain/keychain.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <KConfig>
|
#include <KConfig>
|
||||||
#include <KConfigGroup>
|
#include <KConfigGroup>
|
||||||
@@ -128,16 +132,7 @@ Controller::Controller(QObject *parent)
|
|||||||
if (AccountRegistry::instance().size() > oldAccountCount) {
|
if (AccountRegistry::instance().size() > oldAccountCount) {
|
||||||
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
|
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
|
||||||
connect(connection, &Connection::syncDone, this, [=]() {
|
connect(connection, &Connection::syncDone, this, [=]() {
|
||||||
bool changes = false;
|
handleNotifications(connection);
|
||||||
for (const auto &room : connection->allRooms()) {
|
|
||||||
if (m_notificationCounts[room] != room->unreadStats().notableCount) {
|
|
||||||
m_notificationCounts[room] = room->unreadStats().notableCount;
|
|
||||||
changes = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changes) {
|
|
||||||
handleNotifications();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
oldAccountCount = AccountRegistry::instance().size();
|
oldAccountCount = AccountRegistry::instance().size();
|
||||||
@@ -146,19 +141,16 @@ Controller::Controller(QObject *parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
void Controller::handleNotifications()
|
void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||||
{
|
{
|
||||||
static bool initial = true;
|
static QStringList initial;
|
||||||
static QStringList oldNotifications;
|
static QStringList oldNotifications;
|
||||||
if (!m_connection) {
|
auto job = connection->callApi<GetNotificationsJob>();
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto job = m_connection->callApi<GetNotificationsJob>();
|
|
||||||
|
|
||||||
connect(job, &BaseJob::success, this, [this, job]() {
|
connect(job, &BaseJob::success, this, [job, connection]() {
|
||||||
const auto notifications = job->jsonData()["notifications"].toArray();
|
const auto notifications = job->jsonData()["notifications"].toArray();
|
||||||
if (initial) {
|
if (!initial.contains(connection->user()->id())) {
|
||||||
initial = false;
|
initial.append(connection->user()->id());
|
||||||
for (const auto &n : notifications) {
|
for (const auto &n : notifications) {
|
||||||
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
||||||
}
|
}
|
||||||
@@ -174,7 +166,7 @@ void Controller::handleNotifications()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
oldNotifications += notification["event"].toObject()["event_id"].toString();
|
oldNotifications += notification["event"].toObject()["event_id"].toString();
|
||||||
auto room = m_connection->room(notification["room_id"].toString());
|
auto room = connection->room(notification["room_id"].toString());
|
||||||
|
|
||||||
// If room exists, room is NOT active OR the application is NOT active, show notification
|
// If room exists, room is NOT active OR the application is NOT active, show notification
|
||||||
if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
|
if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
|
||||||
@@ -196,7 +188,7 @@ void Controller::handleNotifications()
|
|||||||
|
|
||||||
if (notification["event"]["type"] == "m.room.encrypted") {
|
if (notification["event"]["type"] == "m.room.encrypted") {
|
||||||
#ifdef Quotient_E2EE_ENABLED
|
#ifdef Quotient_E2EE_ENABLED
|
||||||
auto decrypted = m_connection->decryptNotification(notification);
|
auto decrypted = connection->decryptNotification(notification);
|
||||||
body = decrypted["content"].toObject()["body"].toString();
|
body = decrypted["content"].toObject()["body"].toString();
|
||||||
#endif
|
#endif
|
||||||
if (body.isEmpty()) {
|
if (body.isEmpty()) {
|
||||||
@@ -381,6 +373,11 @@ void Controller::invokeLogin()
|
|||||||
if (error == "Unrecognised access token") {
|
if (error == "Unrecognised access token") {
|
||||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||||
logout(connection, false);
|
logout(connection, false);
|
||||||
|
} else if (error == "Connection closed") {
|
||||||
|
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||||
|
// Failed due to network connection issue. This might happen when the homeserver is
|
||||||
|
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
|
||||||
|
// connect to the homeserver. In this case, we don't want to do logout().
|
||||||
} else {
|
} else {
|
||||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||||
logout(connection, true);
|
logout(connection, true);
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ private:
|
|||||||
|
|
||||||
bool hasWindowSystem() const;
|
bool hasWindowSystem() const;
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
void handleNotifications();
|
void handleNotifications(QPointer<Quotient::Connection> connection);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
#include "emojitones.h"
|
#include "emojitones.h"
|
||||||
#include "emojimodel.h"
|
#include "models/emojimodel.h"
|
||||||
|
|
||||||
QMultiHash<QString, QVariant> EmojiTones::_tones = {
|
QMultiHash<QString, QVariant> EmojiTones::_tones = {
|
||||||
#include "emojitones_data.h"
|
#include "emojitones_data.h"
|
||||||
|
|||||||
42
src/main.cpp
42
src/main.cpp
@@ -42,39 +42,39 @@
|
|||||||
#include "blurhashimageprovider.h"
|
#include "blurhashimageprovider.h"
|
||||||
#include "chatdocumenthandler.h"
|
#include "chatdocumenthandler.h"
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
#include "collapsestateproxymodel.h"
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "customemojimodel.h"
|
|
||||||
#include "devicesmodel.h"
|
|
||||||
#include "emojimodel.h"
|
|
||||||
#include "filetypesingleton.h"
|
#include "filetypesingleton.h"
|
||||||
#include "joinrulesevent.h"
|
#include "joinrulesevent.h"
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
#include "keywordnotificationrulemodel.h"
|
|
||||||
#include "login.h"
|
#include "login.h"
|
||||||
#include "matriximageprovider.h"
|
#include "matriximageprovider.h"
|
||||||
#include "messageeventmodel.h"
|
#include "models/collapsestateproxymodel.h"
|
||||||
#include "messagefiltermodel.h"
|
#include "models/customemojimodel.h"
|
||||||
|
#include "models/devicesmodel.h"
|
||||||
|
#include "models/emojimodel.h"
|
||||||
|
#include "models/keywordnotificationrulemodel.h"
|
||||||
|
#include "models/messageeventmodel.h"
|
||||||
|
#include "models/messagefiltermodel.h"
|
||||||
|
#include "models/publicroomlistmodel.h"
|
||||||
|
#include "models/roomlistmodel.h"
|
||||||
|
#include "models/searchmodel.h"
|
||||||
|
#include "models/serverlistmodel.h"
|
||||||
|
#include "models/sortfilterroomlistmodel.h"
|
||||||
|
#include "models/sortfilterspacelistmodel.h"
|
||||||
|
#include "models/userdirectorylistmodel.h"
|
||||||
|
#include "models/userfiltermodel.h"
|
||||||
|
#include "models/userlistmodel.h"
|
||||||
|
#include "models/webshortcutmodel.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#include "searchmodel.h"
|
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
#endif
|
#endif
|
||||||
#include "publicroomlistmodel.h"
|
|
||||||
#include "roomlistmodel.h"
|
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "serverlistmodel.h"
|
|
||||||
#include "sortfilterroomlistmodel.h"
|
|
||||||
#include "sortfilterspacelistmodel.h"
|
|
||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
#include "urlhelper.h"
|
#include "urlhelper.h"
|
||||||
#include "userdirectorylistmodel.h"
|
|
||||||
#include "userfiltermodel.h"
|
|
||||||
#include "userlistmodel.h"
|
|
||||||
#include "webshortcutmodel.h"
|
|
||||||
#include "windowcontroller.h"
|
#include "windowcontroller.h"
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
#include <keyverificationsession.h>
|
#include <keyverificationsession.h>
|
||||||
@@ -82,15 +82,19 @@
|
|||||||
#ifdef HAVE_COLORSCHEME
|
#ifdef HAVE_COLORSCHEME
|
||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#endif
|
#endif
|
||||||
#include "completionmodel.h"
|
#include "models/completionmodel.h"
|
||||||
|
#include "models/statemodel.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "statemodel.h"
|
|
||||||
|
|
||||||
#ifdef HAVE_RUNNER
|
#ifdef HAVE_RUNNER
|
||||||
#include "runner.h"
|
#include "runner.h"
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
|
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
|
||||||
|
|||||||
@@ -158,11 +158,12 @@ QVector<ActionsModel::Action> actions{
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Invite) {
|
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
||||||
|
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Ban) {
|
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
134
src/models/collapsestateproxymodel.cpp
Normal file
134
src/models/collapsestateproxymodel.cpp
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "collapsestateproxymodel.h"
|
||||||
|
#include "messageeventmodel.h"
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(source_parent);
|
||||||
|
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
||||||
|
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||||
|
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||||
|
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||||
|
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (role == AggregateDisplayRole) {
|
||||||
|
return aggregateEventToString(mapToSource(index).row());
|
||||||
|
} else if (role == StateEventsRole) {
|
||||||
|
return stateEventsList(mapToSource(index).row());
|
||||||
|
} else if (role == AuthorListRole) {
|
||||||
|
return authorList(mapToSource(index).row());
|
||||||
|
}
|
||||||
|
return sourceModel()->data(mapToSource(index), role);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||||
|
{
|
||||||
|
auto roles = sourceModel()->roleNames();
|
||||||
|
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||||
|
roles[StateEventsRole] = "stateEvents";
|
||||||
|
roles[AuthorListRole] = "authorList";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||||
|
{
|
||||||
|
QStringList parts;
|
||||||
|
QVariantList uniqueAuthors;
|
||||||
|
for (int i = sourceRow; i >= 0; i--) {
|
||||||
|
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||||
|
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||||
|
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||||
|
uniqueAuthors.append(nextAuthor);
|
||||||
|
}
|
||||||
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||||
|
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||||
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.sort(); // Sort them so that all identical events can be collected.
|
||||||
|
if (!parts.isEmpty()) {
|
||||||
|
QStringList chunks;
|
||||||
|
while (!parts.isEmpty()) {
|
||||||
|
chunks += QString();
|
||||||
|
int count = 1;
|
||||||
|
auto part = parts.takeFirst();
|
||||||
|
chunks.last() += part;
|
||||||
|
while (!parts.isEmpty() && parts.first() == part) {
|
||||||
|
parts.removeFirst();
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||||
|
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunks.removeDuplicates();
|
||||||
|
QString text = "<style>a {text-decoration: none;}</style>"; // There can be links in the event text so make sure all are styled.
|
||||||
|
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||||
|
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||||
|
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||||
|
.arg(uniqueAuthors[0].toMap()["id"].toString(),
|
||||||
|
uniqueAuthors[0].toMap()["color"].toString(),
|
||||||
|
uniqueAuthors[0].toMap()["displayName"].toString());
|
||||||
|
text += userText;
|
||||||
|
text += chunks.takeFirst();
|
||||||
|
|
||||||
|
if (chunks.size() > 0) {
|
||||||
|
while (chunks.size() > 1) {
|
||||||
|
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||||
|
text += chunks.takeFirst();
|
||||||
|
}
|
||||||
|
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||||
|
text += chunks.takeFirst();
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||||
|
{
|
||||||
|
QVariantList stateEvents;
|
||||||
|
for (int i = sourceRow; i >= 0; i--) {
|
||||||
|
auto nextState = QVariantMap{
|
||||||
|
{"author", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||||
|
{"authorDisplayName", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||||
|
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||||
|
};
|
||||||
|
stateEvents.append(nextState);
|
||||||
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||||
|
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||||
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stateEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||||
|
{
|
||||||
|
QVariantList uniqueAuthors;
|
||||||
|
for (int i = sourceRow; i >= 0; i--) {
|
||||||
|
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||||
|
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||||
|
uniqueAuthors.append(nextAvatar);
|
||||||
|
}
|
||||||
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||||
|
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||||
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueAuthors;
|
||||||
|
}
|
||||||
@@ -12,10 +12,29 @@ class CollapseStateProxyModel : public QSortFilterProxyModel
|
|||||||
public:
|
public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
AggregateDisplayRole = MessageEventModel::LastRole + 1,
|
AggregateDisplayRole = MessageEventModel::LastRole + 1,
|
||||||
|
StateEventsRole,
|
||||||
|
AuthorListRole,
|
||||||
};
|
};
|
||||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief QString aggregating the text of consecutive state events starting at row.
|
||||||
|
*
|
||||||
|
* If state events happen on different days they will be split into two aggregate
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||||
|
/**
|
||||||
|
* @brief Return a list of consecutive state events starting at row.
|
||||||
|
*
|
||||||
|
* If state events happen on different days they will be split into two aggregate
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||||
|
/**
|
||||||
|
* @brief List of unique authors for the aggregate state events starting at row.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QVariantList authorList(int row) const;
|
||||||
};
|
};
|
||||||
@@ -16,7 +16,7 @@ CompletionModel::CompletionModel(QObject *parent)
|
|||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_filterModel(new CompletionProxyModel())
|
, m_filterModel(new CompletionProxyModel())
|
||||||
, m_userListModel(new UserListModel(this))
|
, m_userListModel(new UserListModel(this))
|
||||||
, m_emojiModel(new KConcatenateRowsProxyModel(this))
|
, m_emojiModel(new QConcatenateTablesProxyModel(this))
|
||||||
{
|
{
|
||||||
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
|
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
|
||||||
connect(this, &CompletionModel::roomChanged, this, [this]() {
|
connect(this, &CompletionModel::roomChanged, this, [this]() {
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QConcatenateTablesProxyModel>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
#include <KConcatenateRowsProxyModel>
|
|
||||||
|
|
||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
|
|
||||||
class CompletionProxyModel;
|
class CompletionProxyModel;
|
||||||
@@ -75,6 +74,6 @@ private:
|
|||||||
|
|
||||||
UserListModel *m_userListModel;
|
UserListModel *m_userListModel;
|
||||||
RoomListModel *m_roomListModel;
|
RoomListModel *m_roomListModel;
|
||||||
KConcatenateRowsProxyModel *m_emojiModel;
|
QConcatenateTablesProxyModel *m_emojiModel;
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(CompletionModel::AutoCompletionType);
|
Q_DECLARE_METATYPE(CompletionModel::AutoCompletionType);
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <memory>
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
struct CustomEmoji {
|
struct CustomEmoji {
|
||||||
QString name; // with :semicolons:
|
QString name; // with :semicolons:
|
||||||
@@ -70,7 +70,12 @@ void DevicesModel::logout(int index, const QString &password)
|
|||||||
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
||||||
|
|
||||||
connect(job, &BaseJob::result, this, [this, job, password, index] {
|
connect(job, &BaseJob::result, this, [this, job, password, index] {
|
||||||
if (job->error() != 0) {
|
auto onSuccess = [this, index]() {
|
||||||
|
beginRemoveRows(QModelIndex(), index, index);
|
||||||
|
m_devices.remove(index);
|
||||||
|
endRemoveRows();
|
||||||
|
};
|
||||||
|
if (job->error() != BaseJob::Success) {
|
||||||
QJsonObject replyData = job->jsonData();
|
QJsonObject replyData = job->jsonData();
|
||||||
QJsonObject authData;
|
QJsonObject authData;
|
||||||
authData["session"] = replyData["session"];
|
authData["session"] = replyData["session"];
|
||||||
@@ -79,11 +84,9 @@ void DevicesModel::logout(int index, const QString &password)
|
|||||||
QJsonObject identifier = {{"type", "m.id.user"}, {"user", Controller::instance().activeConnection()->user()->id()}};
|
QJsonObject identifier = {{"type", "m.id.user"}, {"user", Controller::instance().activeConnection()->user()->id()}};
|
||||||
authData["identifier"] = identifier;
|
authData["identifier"] = identifier;
|
||||||
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||||
connect(innerJob, &BaseJob::success, this, [this, index]() {
|
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||||
beginRemoveRows(QModelIndex(), index, index);
|
} else {
|
||||||
m_devices.remove(index);
|
onSuccess();
|
||||||
endRemoveRows();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[IsNameChangeRole] = "isNameChange";
|
roles[IsNameChangeRole] = "isNameChange";
|
||||||
roles[IsAvatarChangeRole] = "isAvatarChange";
|
roles[IsAvatarChangeRole] = "isAvatarChange";
|
||||||
roles[IsRedactedRole] = "isRedacted";
|
roles[IsRedactedRole] = "isRedacted";
|
||||||
|
roles[GenericDisplayRole] = "genericDisplay";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,7 +440,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
case EventTypeRole:
|
case EventTypeRole:
|
||||||
return DelegateType::ReadMarker;
|
return DelegateType::ReadMarker;
|
||||||
case TimeRole: {
|
case TimeRole: {
|
||||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
|
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||||
const KFormat format;
|
const KFormat format;
|
||||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||||
}
|
}
|
||||||
@@ -462,6 +463,14 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return m_currentRoom->eventToString(evt, Qt::RichText);
|
return m_currentRoom->eventToString(evt, Qt::RichText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == GenericDisplayRole) {
|
||||||
|
if (evt.isRedacted()) {
|
||||||
|
return i18n("<i>[This message was deleted]</i>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_currentRoom->eventToGenericString(evt);
|
||||||
|
}
|
||||||
|
|
||||||
if (role == FormattedBodyRole) {
|
if (role == FormattedBodyRole) {
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
if (e->hasTextContent() && e->mimeType().name() != "text/plain") {
|
if (e->hasTextContent() && e->mimeType().name() != "text/plain") {
|
||||||
@@ -937,24 +946,12 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
|||||||
if (content.contains("m.new_content")) {
|
if (content.contains("m.new_content")) {
|
||||||
// The message has been edited so we have to return the id of the original message instead of the replacement
|
// The message has been edited so we have to return the id of the original message instead of the replacement
|
||||||
eventId = content["m.relates_to"].toObject()["event_id"].toString();
|
eventId = content["m.relates_to"].toObject()["event_id"].toString();
|
||||||
e = eventCast<const RoomMessageEvent>(m_currentRoom->findInTimeline(eventId)->event());
|
|
||||||
if (!e) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
content = e->contentJson();
|
|
||||||
} else {
|
} else {
|
||||||
// For any message that isn't an edit return the id of the current message
|
// For any message that isn't an edit return the id of the current message
|
||||||
eventId = (*it)->id();
|
eventId = (*it)->id();
|
||||||
}
|
}
|
||||||
targetMessage.insert("event_id", eventId);
|
targetMessage.insert("event_id", eventId);
|
||||||
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
||||||
|
|
||||||
// keep reply relationship
|
|
||||||
if (content.contains("m.relates_to")) {
|
|
||||||
targetMessage.insert("m.relates_to", content["m.relates_to"].toObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to get the message from the original eventId or body will have * on the front
|
// Need to get the message from the original eventId or body will have * on the front
|
||||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
||||||
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
||||||
@@ -45,6 +45,7 @@ public:
|
|||||||
AnnotationRole,
|
AnnotationRole,
|
||||||
UserMarkerRole,
|
UserMarkerRole,
|
||||||
FormattedBodyRole,
|
FormattedBodyRole,
|
||||||
|
GenericDisplayRole,
|
||||||
|
|
||||||
MimeTypeRole,
|
MimeTypeRole,
|
||||||
FileMimetypeIcon,
|
FileMimetypeIcon,
|
||||||
@@ -16,7 +16,7 @@ class Connection;
|
|||||||
class UserDirectoryListModel : public QAbstractListModel
|
class UserDirectoryListModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
||||||
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged)
|
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged)
|
||||||
|
|
||||||
@@ -95,48 +95,35 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == ObjectRole) {
|
if (role == ObjectRole) {
|
||||||
return QVariant::fromValue(user);
|
return QVariant::fromValue(user);
|
||||||
}
|
}
|
||||||
if (role == PermRole) {
|
|
||||||
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
|
||||||
auto userPl = pl->powerLevelForUser(user->id());
|
|
||||||
|
|
||||||
if (userPl == pl->content().usersDefault) { // Shortcut
|
|
||||||
return UserType::Member;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl < pl->powerLevelForEvent("m.room.message")) {
|
|
||||||
return UserType::Muted;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userPls = pl->users();
|
|
||||||
|
|
||||||
int highestPl = pl->usersDefault();
|
|
||||||
QHash<QString, int>::const_iterator i = userPls.constBegin();
|
|
||||||
while (i != userPls.constEnd()) {
|
|
||||||
if (i.value() > highestPl) {
|
|
||||||
highestPl = i.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl == highestPl) {
|
|
||||||
return UserType::Owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl >= pl->powerLevelForState("m.room.power_levels")) {
|
|
||||||
return UserType::Admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPl >= pl->ban() || userPl >= pl->kick() || userPl >= pl->redact()) {
|
|
||||||
return UserType::Moderator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return UserType::Member;
|
|
||||||
}
|
|
||||||
if (role == PowerLevelRole) {
|
if (role == PowerLevelRole) {
|
||||||
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
||||||
return pl->powerLevelForUser(user->id());
|
return pl->powerLevelForUser(user->id());
|
||||||
}
|
}
|
||||||
|
if (role == PowerLevelStringRole) {
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
||||||
|
#else
|
||||||
|
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
||||||
|
#endif
|
||||||
|
// User might not in the room yet, in this case pl can be nullptr.
|
||||||
|
// e.g. When invited but user not accepted or denied the invitation.
|
||||||
|
if (!pl) {
|
||||||
|
return QStringLiteral("Not Available");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto userPl = pl->powerLevelForUser(user->id());
|
||||||
|
|
||||||
|
switch (userPl) {
|
||||||
|
case 0:
|
||||||
|
return QStringLiteral("Member");
|
||||||
|
case 50:
|
||||||
|
return QStringLiteral("Moderator");
|
||||||
|
case 100:
|
||||||
|
return QStringLiteral("Admin");
|
||||||
|
default:
|
||||||
|
return QStringLiteral("Custom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -241,8 +228,8 @@ QHash<int, QByteArray> UserListModel::roleNames() const
|
|||||||
roles[UserIdRole] = "userId";
|
roles[UserIdRole] = "userId";
|
||||||
roles[AvatarRole] = "avatar";
|
roles[AvatarRole] = "avatar";
|
||||||
roles[ObjectRole] = "user";
|
roles[ObjectRole] = "user";
|
||||||
roles[PermRole] = "perm";
|
|
||||||
roles[PowerLevelRole] = "powerLevel";
|
roles[PowerLevelRole] = "powerLevel";
|
||||||
|
roles[PowerLevelStringRole] = "powerLevelString";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ public:
|
|||||||
Moderator,
|
Moderator,
|
||||||
Member,
|
Member,
|
||||||
Muted,
|
Muted,
|
||||||
|
Custom,
|
||||||
};
|
};
|
||||||
Q_ENUM(Types)
|
Q_ENUM(Types)
|
||||||
};
|
};
|
||||||
@@ -41,8 +43,8 @@ public:
|
|||||||
UserIdRole,
|
UserIdRole,
|
||||||
AvatarRole,
|
AvatarRole,
|
||||||
ObjectRole,
|
ObjectRole,
|
||||||
PermRole,
|
|
||||||
PowerLevelRole,
|
PowerLevelRole,
|
||||||
|
PowerLevelStringRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
UserListModel(QObject *parent = nullptr);
|
UserListModel(QObject *parent = nullptr);
|
||||||
@@ -57,8 +59,6 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
// Q_INVOKABLE
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void roomChanged();
|
void roomChanged();
|
||||||
void usersRefreshed();
|
void usersRefreshed();
|
||||||
@@ -71,7 +71,7 @@ private Q_SLOTS:
|
|||||||
void avatarChanged(Quotient::User *user, const Quotient::Room *context);
|
void avatarChanged(Quotient::User *user, const Quotient::Room *context);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NeoChatRoom *m_currentRoom;
|
QPointer<NeoChatRoom> m_currentRoom;
|
||||||
QList<Quotient::User *> m_users;
|
QList<Quotient::User *> m_users;
|
||||||
|
|
||||||
int findUserPos(Quotient::User *user) const;
|
int findUserPos(Quotient::User *user) const;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user