Compare commits
43 Commits
work/tobia
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45c46ddcbb | ||
|
|
31d83ac0e3 | ||
|
|
909eec30d2 | ||
|
|
dbed3e99c2 | ||
|
|
4d0db0b5c2 | ||
|
|
6e49aaf17b | ||
|
|
1092d75f2e | ||
|
|
8059c3797d | ||
|
|
1302d62ad9 | ||
|
|
8eaae4034d | ||
|
|
354e3414a1 | ||
|
|
fc24beae6d | ||
|
|
923cc67b55 | ||
|
|
30e24069bc | ||
|
|
f7533a454c | ||
|
|
ab4e1a86dc | ||
|
|
d28c2ed113 | ||
|
|
3a467328f5 | ||
|
|
979d83cb01 | ||
|
|
d165cd955d | ||
|
|
6eb770343e | ||
|
|
54be52b855 | ||
|
|
d201333409 | ||
|
|
3db8b4cd17 | ||
|
|
0e246a00bc | ||
|
|
e638fa8929 | ||
|
|
cdc982ad91 | ||
|
|
bff69e21ad | ||
|
|
e117cc0cfb | ||
|
|
3af1a88e05 | ||
|
|
aa116a35f5 | ||
|
|
ac232d7f55 | ||
|
|
928911e33c | ||
|
|
e3c30f5bb3 | ||
|
|
4b51855528 | ||
|
|
79c27db0a9 | ||
|
|
78e42ab352 | ||
|
|
93909c45ee | ||
|
|
eda0bf4b23 | ||
|
|
4f4b10e0b6 | ||
|
|
2b374a8bec | ||
|
|
7821de4a8d | ||
|
|
86b88c851f |
@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.6")
|
||||
set(KF_MIN_VERSION "6.5")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -46,6 +46,7 @@ private Q_SLOTS:
|
||||
void sendCustomEmojiCode_data();
|
||||
void sendCustomEmojiCode();
|
||||
|
||||
void receiveSpacelessSelfClosingTag();
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
@@ -252,6 +253,19 @@ void TextHandlerTest::sendCustomEmojiCode()
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveSpacelessSelfClosingTag()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testRichOutputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testPlainOutputString = QStringLiteral("Test...\n...ing");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testRichOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testPlainOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripReply()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
@@ -449,6 +463,9 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
QString testOutputStringMxId = QStringLiteral(
|
||||
"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>");
|
||||
|
||||
QString testInputStringMxIdWithPrefix = QStringLiteral("a @user:kde.org b");
|
||||
QString testOutputStringMxIdWithPrefix = QStringLiteral("a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputStringLink1);
|
||||
|
||||
@@ -462,6 +479,9 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
|
||||
testTextHandler.setData(testInputStringMxId);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||
|
||||
testTextHandler.setData(testInputStringMxIdWithPrefix);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited_data()
|
||||
@@ -536,7 +556,7 @@ void TextHandlerTest::componentOutput_data()
|
||||
"someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you "
|
||||
"defined the id")
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like<br/>"), {}},
|
||||
MessageComponent{
|
||||
MessageComponentType::Code,
|
||||
QStringLiteral(
|
||||
|
||||
@@ -17,7 +17,6 @@ class WindowControllerTest : public QObject
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullWindow();
|
||||
void geometry();
|
||||
void showAndRaise();
|
||||
void toggle();
|
||||
|
||||
@@ -30,32 +29,10 @@ void WindowControllerTest::nullWindow()
|
||||
auto &instance = WindowController::instance();
|
||||
QCOMPARE(instance.window(), nullptr);
|
||||
|
||||
instance.restoreGeometry();
|
||||
instance.saveGeometry();
|
||||
instance.showAndRaiseWindow({});
|
||||
instance.toggleWindow();
|
||||
}
|
||||
|
||||
void WindowControllerTest::geometry()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
QWindow window;
|
||||
window.setGeometry(0, 0, 200, 200);
|
||||
instance.setWindow(&window);
|
||||
QCOMPARE(instance.window(), &window);
|
||||
|
||||
instance.saveGeometry();
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
|
||||
|
||||
window.setGeometry(0, 0, 400, 400);
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
|
||||
instance.restoreGeometry();
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
|
||||
}
|
||||
|
||||
void WindowControllerTest::showAndRaise()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
@@ -429,6 +429,7 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.08.1" date="2024-09-12"/>
|
||||
<release version="24.08.0" date="2024-08-22"/>
|
||||
<release version="24.05.2" date="2024-07-04"/>
|
||||
<release version="24.05.1" date="2024-06-13"/>
|
||||
|
||||
799
po/ar/neochat.po
799
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
836
po/az/neochat.po
836
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
777
po/ca/neochat.po
777
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
787
po/cs/neochat.po
787
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
790
po/da/neochat.po
790
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
831
po/de/neochat.po
831
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
828
po/el/neochat.po
828
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
825
po/eo/neochat.po
825
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/es/neochat.po
780
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
778
po/eu/neochat.po
778
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
824
po/fi/neochat.po
824
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
786
po/fr/neochat.po
786
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
776
po/gl/neochat.po
776
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
815
po/hu/neochat.po
815
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
796
po/ia/neochat.po
796
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
830
po/id/neochat.po
830
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
814
po/ie/neochat.po
814
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
831
po/it/neochat.po
831
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
706
po/ja/neochat.po
706
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
778
po/ka/neochat.po
778
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
824
po/ko/neochat.po
824
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
706
po/lt/neochat.po
706
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
816
po/lv/neochat.po
816
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
782
po/nl/neochat.po
782
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
792
po/nn/neochat.po
792
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
837
po/pa/neochat.po
837
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/pl/neochat.po
780
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
828
po/pt/neochat.po
828
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
825
po/ru/neochat.po
825
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
837
po/sk/neochat.po
837
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
779
po/sl/neochat.po
779
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/sv/neochat.po
780
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
923
po/ta/neochat.po
923
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
777
po/tr/neochat.po
777
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
784
po/uk/neochat.po
784
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -242,11 +242,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/EditMenu.qml
|
||||
qml/MessageDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenuMobile.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ReportSheet.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RemoveSheet.qml
|
||||
qml/BanSheet.qml
|
||||
qml/RoomSearchPage.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/TimelineView.qml
|
||||
@@ -291,6 +289,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/AskDirectChatConfirmation.qml
|
||||
qml/HoverLinkIndicator.qml
|
||||
qml/AvatarNotification.qml
|
||||
qml/ReasonDialog.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
@@ -308,7 +307,7 @@ add_subdirectory(devtools)
|
||||
add_subdirectory(login)
|
||||
add_subdirectory(chatbar)
|
||||
|
||||
if(UNIX)
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
|
||||
@@ -28,7 +28,8 @@ void ColorSchemer::apply(int idx)
|
||||
|
||||
int ColorSchemer::indexForCurrentScheme()
|
||||
{
|
||||
return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
return -1;
|
||||
// return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
|
||||
@@ -154,7 +154,8 @@ bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
|
||||
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||
} else if (roomMemberEvent->isRename() && roomMemberEvent->prevContent() && roomMemberEvent->prevContent()->membership == roomMemberEvent->membership()
|
||||
&& !NeoChatConfig::self()->showRename()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
|
||||
&& !NeoChatConfig::self()->showAvatarUpdate()) {
|
||||
@@ -366,9 +367,13 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
|
||||
}
|
||||
return (e.senderId() != e.userId())
|
||||
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
||||
: i18n("left the room");
|
||||
if (e.senderId() == e.userId()) {
|
||||
return i18n("left the room");
|
||||
}
|
||||
if (const auto &reason = e.contentJson()["reason"_ls].toString().toHtmlEscaped(); !reason.isEmpty()) {
|
||||
return i18n("has put %1 out of the room: %2", subjectName, reason);
|
||||
}
|
||||
return i18n("has put %1 out of the room", subjectName);
|
||||
case Membership::Ban:
|
||||
if (e.senderId() != e.userId()) {
|
||||
if (e.reason().isEmpty()) {
|
||||
|
||||
@@ -122,7 +122,7 @@ Kirigami.Page {
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.name: "im-kick-user"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
|
||||
@@ -304,7 +304,6 @@ int main(int argc, char *argv[])
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
WindowController::instance().setWindow(window);
|
||||
WindowController::instance().restoreGeometry();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ QList<ActionsModel::Action> actions{
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
|
||||
return QString();
|
||||
}
|
||||
if (room->members().contains(room->member(text))) {
|
||||
if (room->joinedMemberIds().contains(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -2,20 +2,40 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "permissionsmodel.h"
|
||||
#include "powerlevel.h"
|
||||
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
|
||||
#include "powerlevel.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto UsersDefaultKey = "users_default"_L1;
|
||||
constexpr auto StateDefaultKey = "state_default"_L1;
|
||||
constexpr auto EventsDefaultKey = "events_default"_L1;
|
||||
|
||||
constexpr auto InviteKey = "invite"_L1;
|
||||
constexpr auto KickKey = "kick"_L1;
|
||||
constexpr auto BanKey = "ban"_L1;
|
||||
constexpr auto RedactKey = "redact"_L1;
|
||||
|
||||
static const QStringList defaultPermissions = {
|
||||
QStringLiteral("users_default"),
|
||||
QStringLiteral("state_default"),
|
||||
QStringLiteral("events_default"),
|
||||
QStringLiteral("invite"),
|
||||
QStringLiteral("kick"),
|
||||
QStringLiteral("ban"),
|
||||
QStringLiteral("redact"),
|
||||
UsersDefaultKey,
|
||||
StateDefaultKey,
|
||||
EventsDefaultKey,
|
||||
};
|
||||
|
||||
static const QStringList basicPermissions = {
|
||||
InviteKey,
|
||||
KickKey,
|
||||
BanKey,
|
||||
RedactKey,
|
||||
};
|
||||
|
||||
static const QStringList knownPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
QStringLiteral("m.room.power_levels"),
|
||||
@@ -33,14 +53,14 @@ static const QStringList defaultPermissions = {
|
||||
};
|
||||
|
||||
// Alternate name text for default permissions.
|
||||
static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")},
|
||||
{QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")},
|
||||
{QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")},
|
||||
{QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
static const QHash<QString, KLazyLocalizedString> permissionNames = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "Default user power level")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{InviteKey, kli18nc("Room permission type", "Invite users")},
|
||||
{KickKey, kli18nc("Room permission type", "Kick users")},
|
||||
{BanKey, kli18nc("Room permission type", "Ban users")},
|
||||
{RedactKey, kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
{QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")},
|
||||
{QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")},
|
||||
{QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")},
|
||||
@@ -58,10 +78,10 @@ static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
};
|
||||
|
||||
// Subtitles for the default values.
|
||||
static const QHash<QString, KLazyLocalizedString> defaultSubtitles = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
static const QHash<QString, KLazyLocalizedString> permissionSubtitles = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
};
|
||||
|
||||
// Permissions that should use the event default.
|
||||
@@ -70,6 +90,7 @@ static const QStringList eventPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
};
|
||||
};
|
||||
|
||||
PermissionsModel::PermissionsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
@@ -109,6 +130,8 @@ void PermissionsModel::initializeModel()
|
||||
}
|
||||
|
||||
m_permissions.append(defaultPermissions);
|
||||
m_permissions.append(basicPermissions);
|
||||
m_permissions.append(knownPermissions);
|
||||
|
||||
for (const auto &event : currentPowerLevelEvent->events().keys()) {
|
||||
if (!m_permissions.contains(event)) {
|
||||
@@ -131,17 +154,17 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto permission = m_permissions.value(index.row());
|
||||
if (role == NameRole) {
|
||||
if (defaultPermissionNames.keys().contains(permission)) {
|
||||
return defaultPermissionNames.value(permission).toString();
|
||||
if (permissionNames.keys().contains(permission)) {
|
||||
return permissionNames.value(permission).toString();
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
if (role == SubtitleRole) {
|
||||
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
|
||||
if (knownPermissions.contains(permission) && permissionNames.keys().contains(permission)) {
|
||||
return permission;
|
||||
}
|
||||
if (defaultSubtitles.contains(permission)) {
|
||||
return defaultSubtitles.value(permission).toString();
|
||||
if (permissionSubtitles.contains(permission)) {
|
||||
return permissionSubtitles.value(permission).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -166,11 +189,10 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
return QString();
|
||||
}
|
||||
if (role == IsDefaultValueRole) {
|
||||
return permission.contains(QLatin1String("default"));
|
||||
return defaultPermissions.contains(permission);
|
||||
}
|
||||
if (role == IsBasicPermissionRole) {
|
||||
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|
||||
|| permission == QStringLiteral("redact");
|
||||
return basicPermissions.contains(permission);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -201,19 +223,19 @@ std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
|
||||
}
|
||||
|
||||
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||
if (permission == QStringLiteral("ban")) {
|
||||
if (permission == BanKey) {
|
||||
return currentPowerLevelEvent->ban();
|
||||
} else if (permission == QStringLiteral("kick")) {
|
||||
} else if (permission == KickKey) {
|
||||
return currentPowerLevelEvent->kick();
|
||||
} else if (permission == QStringLiteral("invite")) {
|
||||
} else if (permission == InviteKey) {
|
||||
return currentPowerLevelEvent->invite();
|
||||
} else if (permission == QStringLiteral("redact")) {
|
||||
} else if (permission == RedactKey) {
|
||||
return currentPowerLevelEvent->redact();
|
||||
} else if (permission == QStringLiteral("users_default")) {
|
||||
} else if (permission == UsersDefaultKey) {
|
||||
return currentPowerLevelEvent->usersDefault();
|
||||
} else if (permission == QStringLiteral("state_default")) {
|
||||
} else if (permission == StateDefaultKey) {
|
||||
return currentPowerLevelEvent->stateDefault();
|
||||
} else if (permission == QStringLiteral("events_default")) {
|
||||
} else if (permission == EventsDefaultKey) {
|
||||
return currentPowerLevelEvent->eventsDefault();
|
||||
} else if (eventPermissions.contains(permission)) {
|
||||
return currentPowerLevelEvent->powerLevelForEvent(permission);
|
||||
@@ -241,6 +263,9 @@ void PermissionsModel::setPowerLevel(const QString &permission, const int &newPo
|
||||
auto powerLevelContent = currentPowerLevelEvent->contentJson();
|
||||
if (powerLevelContent.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
// Deal with the case where a default or basic permission is missing from the event content erroneously.
|
||||
} else if (defaultPermissions.contains(permission) || basicPermissions.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
} else {
|
||||
auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject();
|
||||
eventPowerLevels[permission] = clampPowerLevel;
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "reactionmodel.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFont>
|
||||
#ifdef HAVE_ICU
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextCharFormat>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/urename.h>
|
||||
#endif
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -154,30 +149,6 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
bool isEmoji(const QString &text)
|
||||
{
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(QString text) const
|
||||
{
|
||||
text = text.toHtmlEscaped();
|
||||
@@ -191,7 +162,7 @@ QString ReactionModel::reactionText(QString text) const
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
|
||||
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
return Utils::isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
}
|
||||
|
||||
#include "moc_reactionmodel.cpp"
|
||||
|
||||
@@ -125,7 +125,7 @@ void WebShortcutModel::trigger(const QString &data)
|
||||
void WebShortcutModel::configureWebShortcuts()
|
||||
{
|
||||
#ifdef HAVE_KIO
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
job->exec();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
|
||||
if (NeoChatConfig::rejectUnknownInvites()) {
|
||||
auto job = this->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
|
||||
connect(job, &BaseJob::result, this, [this, job, roomMemberEvent, showNotification] {
|
||||
connect(job, &BaseJob::result, this, [this, job, showNotification] {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
if (replyData.contains(QStringLiteral("joined"))) {
|
||||
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
|
||||
|
||||
@@ -86,7 +86,7 @@ QQC2.Menu {
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Logout")
|
||||
icon.name: "list-remove-user"
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: confirmLogoutDialogComponent.createObject(QQC2.ApplicationWindow.window.overlay).open()
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Kirigami.Dialog {
|
||||
if (switchUserButton.checked) {
|
||||
switchUserButton.checked = false;
|
||||
}
|
||||
accountView.currentIndex = Controller.activeConnectionIndex;
|
||||
root.close();
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
accountView.currentIndex = accountView.count - 1;
|
||||
|
||||
@@ -38,20 +38,20 @@ ColumnLayout {
|
||||
text: i18n("Edit")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: root.attachmentPath
|
||||
}
|
||||
}
|
||||
// Component {
|
||||
// id: imageEditorPage
|
||||
// ImageEditorPage {
|
||||
// imagePath: root.attachmentPath
|
||||
// }
|
||||
// }
|
||||
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function (newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
root.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
// onClicked: {
|
||||
// let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
|
||||
// imageEditor.newPathChanged.connect(function (newPath) {
|
||||
// applicationWindow().pageStack.layers.pop();
|
||||
// root.attachmentPath = newPath;
|
||||
// });
|
||||
// }
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ Loader {
|
||||
*/
|
||||
property list<Kirigami.Action> actions
|
||||
|
||||
/**
|
||||
* @brief Whether the web search menu should be shown or not.
|
||||
*/
|
||||
property bool enableWebSearch: true
|
||||
|
||||
/**
|
||||
* Some common actions shared between menus
|
||||
*/
|
||||
@@ -82,16 +87,23 @@ Loader {
|
||||
|
||||
component RemoveMessageAction: Kirigami.Action {
|
||||
visible: author.isLocalMember || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
text: i18nc("@action:button", "Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
|
||||
room: currentRoom,
|
||||
eventId: eventId
|
||||
}, {
|
||||
title: i18nc("@title", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.redactEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
component ReplyMessageAction: Kirigami.Action {
|
||||
@@ -108,13 +120,28 @@ Loader {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
visible: !author.isLocalMember
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), {
|
||||
room: currentRoom,
|
||||
eventId: eventId
|
||||
}, {
|
||||
title: i18nc("@title", "Report Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Report Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for reporting this message"),
|
||||
icon: "dialog-warning-symbolic",
|
||||
actionText: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
}, {
|
||||
title: i18nc("@title", "Report Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.reportEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
component ShowUserAction: Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Show User")
|
||||
icon.name: "username-copy"
|
||||
onTriggered: {
|
||||
RoomManager.resolveResource(author.id)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -128,6 +155,7 @@ Loader {
|
||||
id: menuItem
|
||||
visible: modelData.visible
|
||||
title: modelData.text
|
||||
icon: modelData.icon
|
||||
|
||||
Instantiator {
|
||||
model: modelData.children
|
||||
@@ -158,7 +186,8 @@ Loader {
|
||||
QQC2.Menu {
|
||||
id: webshortcutmenu
|
||||
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
||||
property bool isVisible: webshortcutmodel.enabled
|
||||
icon.name: "search-symbolic"
|
||||
property bool isVisible: webshortcutmodel.enabled && root.enableWebSearch
|
||||
Component.onCompleted: {
|
||||
webshortcutmenu.parent.visible = isVisible;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ DelegateContextMenu {
|
||||
*/
|
||||
required property var progressInfo
|
||||
|
||||
// Web search isn't useful for images
|
||||
enableWebSearch: false
|
||||
|
||||
/**
|
||||
* @brief The main list of menu item actions.
|
||||
*
|
||||
@@ -67,15 +70,23 @@ DelegateContextMenu {
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
|
||||
room: currentRoom,
|
||||
eventId: eventId
|
||||
}, {
|
||||
title: i18nc("@title", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.redactEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ReportMessageAction {},
|
||||
DelegateContextMenu.ShowUserAction {},
|
||||
DelegateContextMenu.ViewSourceAction {}
|
||||
]
|
||||
|
||||
|
||||
116
src/qml/FileDelegateContextMenuMobile.qml
Normal file
116
src/qml/FileDelegateContextMenuMobile.qml
Normal file
@@ -0,0 +1,116 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import Qt.labs.platform
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
/**
|
||||
* @brief The menu for media messages.
|
||||
*
|
||||
* This component just overloads the actions and nested actions of the base menu
|
||||
* to what is required for a media item.
|
||||
*
|
||||
* @sa DelegateContextMenu
|
||||
*/
|
||||
DelegateContextMenu {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The MIME type of the media.
|
||||
*/
|
||||
property string mimeType
|
||||
|
||||
/**
|
||||
* @brief Progress info when downloading files.
|
||||
*
|
||||
* @sa Quotient::FileTransferInfo
|
||||
*/
|
||||
required property var progressInfo
|
||||
|
||||
// Web search isn't useful for images
|
||||
enableWebSearch: false
|
||||
|
||||
/**
|
||||
* @brief The main list of menu item actions.
|
||||
*
|
||||
* Each action will be instantiated as a single line in the menu.
|
||||
*/
|
||||
property list<Kirigami.Action> actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Open Externally")
|
||||
icon.name: "document-open"
|
||||
onTriggered: {
|
||||
currentRoom.openEventMediaExternally(root.eventId);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Save As")
|
||||
icon.name: "document-save"
|
||||
onTriggered: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||
dialog.open();
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId);
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ReplyMessageAction {},
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: {
|
||||
currentRoom.copyEventMedia(root.eventId);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.redactEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ReportMessageAction {},
|
||||
DelegateContextMenu.ShowUserAction {},
|
||||
DelegateContextMenu.ViewSourceAction {}
|
||||
]
|
||||
|
||||
/**
|
||||
* @brief The list of menu item actions that have sub-actions.
|
||||
*
|
||||
* Each action will be instantiated as a single line that opens a sub menu.
|
||||
*/
|
||||
property list<Kirigami.Action> nestedActions: [
|
||||
]
|
||||
|
||||
Component {
|
||||
id: saveAsDialog
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
if (!currentFile) {
|
||||
return;
|
||||
}
|
||||
NeoChatConfig.lastSaveDirectory = folder;
|
||||
NeoChatConfig.save();
|
||||
currentRoom.downloadFile(eventId, currentFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -11,111 +13,77 @@ import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
SearchPage {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
|
||||
title: i18n("Invite a User")
|
||||
title: i18nc("@title:dialog", "Invite a User")
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "dialog-close"
|
||||
text: i18nc("@action", "Cancel")
|
||||
onTriggered: root.closeDialog()
|
||||
}
|
||||
]
|
||||
header: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
searchFieldPlaceholder: i18nc("@info:placeholder", "Find a user…")
|
||||
noResultPlaceholderMessage: i18nc("@info:placeholder", "No users found")
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: identifierField
|
||||
property bool isUserId: text.match(/@(.+):(.+)/g)
|
||||
Layout.fillWidth: true
|
||||
headerTrailing: QQC2.Button {
|
||||
icon.name: "list-add"
|
||||
display: QQC2.Button.IconOnly
|
||||
enabled: root.model.searchText.match(/@(.+):(.+)/g) && !root.room.containsUser(root.model.searchText)
|
||||
|
||||
placeholderText: i18n("Find a user...")
|
||||
onAccepted: userDictListModel.search()
|
||||
}
|
||||
text: i18nc("@action:button", "Invite this User")
|
||||
|
||||
QQC2.Button {
|
||||
visible: identifierField.isUserId
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: root.room.containsUser(root.model.searchText) ? i18nc("@info:tooltip", "User is either already a member or has been invited") : text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
text: i18n("Add")
|
||||
highlighted: true
|
||||
|
||||
onClicked: {
|
||||
room.inviteToRoom(identifierField.text);
|
||||
}
|
||||
}
|
||||
onClicked: root.room.inviteToRoom(root.model.searchText);
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: userDictListView
|
||||
model: UserDirectoryListModel {
|
||||
id: userDictListModel
|
||||
|
||||
clip: true
|
||||
connection: root.room.connection
|
||||
}
|
||||
|
||||
model: UserDirectoryListModel {
|
||||
id: userDictListModel
|
||||
modelDelegate: Delegates.RoundedItemDelegate {
|
||||
id: delegate
|
||||
|
||||
connection: root.room.connection
|
||||
searchText: identifierField.text
|
||||
}
|
||||
required property string userId
|
||||
required property string displayName
|
||||
required property url avatarUrl
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
text: displayName
|
||||
|
||||
visible: userDictListView.count < 1
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
source: delegate.avatarUrl
|
||||
name: delegate.displayName
|
||||
}
|
||||
|
||||
text: i18n("No users available")
|
||||
}
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: delegate
|
||||
subtitle: delegate.userId
|
||||
labelItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
id: delegate
|
||||
QQC2.ToolButton {
|
||||
id: inviteButton
|
||||
|
||||
required property string userId
|
||||
required property string displayName
|
||||
required property url avatarUrl
|
||||
readonly property bool inRoom: root.room && root.room.containsUser(delegate.userId)
|
||||
|
||||
property bool inRoom: room && room.containsUser(userId)
|
||||
icon.name: "document-send"
|
||||
text: i18nc("@action:button", "Send invitation")
|
||||
opacity: inRoom ? 0.5 : 1
|
||||
enabled: !inRoom
|
||||
|
||||
text: displayName
|
||||
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
source: delegate.avatarUrl
|
||||
name: delegate.displayName
|
||||
onClicked: {
|
||||
inviteButton.enabled = false;
|
||||
root.room.inviteToRoom(delegate.userId);
|
||||
}
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: delegate
|
||||
subtitle: delegate.userId
|
||||
labelItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: inviteButton
|
||||
icon.name: "document-send"
|
||||
text: i18n("Send invitation")
|
||||
checkable: true
|
||||
checked: inRoom
|
||||
opacity: inRoom ? 0.5 : 1
|
||||
|
||||
onToggled: {
|
||||
if (inRoom) {
|
||||
checked = true;
|
||||
} else {
|
||||
room.inviteToRoom(delegate.userId);
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: !inRoom ? text : i18n("User is either already a member or has been invited")
|
||||
QQC2.ToolTip.visible: inviteButton.hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.ToolTip.text: !inRoom ? text : i18nc("@info:tooltip", "User is either already a member or has been invited")
|
||||
QQC2.ToolTip.visible: inviteButton.hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,36 @@ Components.AbstractMaximizeComponent {
|
||||
root.close();
|
||||
}
|
||||
enabled: !!root.location
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:intoolbar Re-center the map onto the set location", "Re-Center")
|
||||
icon.name: "snap-bounding-box-center-symbolic"
|
||||
onTriggered: mapView.map.fitViewportToMapItems([mapView.locationMapItem])
|
||||
enabled: root.location !== undefined
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:intoolbar Determine the device's location", "Locate")
|
||||
icon.name: "mark-location-symbolic"
|
||||
enabled: positionSource.valid
|
||||
onTriggered: positionSource.update()
|
||||
}
|
||||
]
|
||||
|
||||
PositionSource {
|
||||
id: positionSource
|
||||
|
||||
active: false
|
||||
|
||||
onPositionChanged: {
|
||||
const coord = position.coordinate;
|
||||
mapView.gpsMapItem.latitude = coord.latitude;
|
||||
mapView.gpsMapItem.longitude = coord.longitude;
|
||||
|
||||
mapView.map.addMapItem(mapView.gpsMapItem);
|
||||
mapView.map.fitViewportToMapItems([mapView.gpsMapItem])
|
||||
}
|
||||
}
|
||||
|
||||
content: MapView {
|
||||
id: mapView
|
||||
map.plugin: OsmLocationPlugin.plugin
|
||||
@@ -54,6 +81,15 @@ Components.AbstractMaximizeComponent {
|
||||
author: null
|
||||
}
|
||||
|
||||
readonly property LocationMapItem gpsMapItem: LocationMapItem {
|
||||
latitude: 0.0
|
||||
longitude: 0.0
|
||||
isLive: true
|
||||
heading: NaN
|
||||
asset: ""
|
||||
author: null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: mapView.map
|
||||
function onCopyrightLinkActivated(link: string) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.config as KConfig
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.login
|
||||
@@ -68,36 +69,8 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// This timer allows to batch update the window size change to reduce
|
||||
// the io load and also work around the fact that x/y/width/height are
|
||||
// changed when loading the page and overwrite the saved geometry from
|
||||
// the previous session.
|
||||
Timer {
|
||||
id: saveWindowGeometryTimer
|
||||
interval: 1000
|
||||
onTriggered: WindowController.saveGeometry()
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: saveWindowGeometryConnections
|
||||
enabled: false // Disable on startup to avoid writing wrong values if the window is hidden
|
||||
target: root
|
||||
|
||||
function onClosing() {
|
||||
WindowController.saveGeometry();
|
||||
}
|
||||
function onWidthChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
function onHeightChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
function onXChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
function onYChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
KConfig.WindowStateSaver {
|
||||
configGroupName: "Window"
|
||||
}
|
||||
|
||||
QuickSwitcher {
|
||||
@@ -198,11 +171,9 @@ Kirigami.ApplicationWindow {
|
||||
if (ShareHandler.text && root.connection) {
|
||||
root.handleShare()
|
||||
}
|
||||
if (NeoChatConfig.minimizeToSystemTrayOnStartup && !Kirigami.Settings.isMobile && Controller.supportSystemTray && NeoChatConfig.systemTray) {
|
||||
restoreWindowGeometryConnections.enabled = true; // To restore window size and position
|
||||
} else {
|
||||
const hasSystemTray = Controller.supportSystemTray && NeoChatConfig.systemTray;
|
||||
if (Kirigami.Settings.isMobile || !(hasSystemTray && NeoChatConfig.minimizeToSystemTrayOnStartup)) {
|
||||
visible = true;
|
||||
saveWindowGeometryConnections.enabled = true;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
@@ -276,22 +247,7 @@ Kirigami.ApplicationWindow {
|
||||
target: Controller
|
||||
|
||||
function onErrorOccured(error, detail) {
|
||||
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: restoreWindowGeometryConnections
|
||||
enabled: false
|
||||
target: root
|
||||
|
||||
function onVisibleChanged() {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
Controller.restoreWindowGeometry(root);
|
||||
restoreWindowGeometryConnections.enabled = false; // Only restore window geometry for the first time
|
||||
saveWindowGeometryConnections.enabled = true;
|
||||
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error, "short");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,13 +66,7 @@ DelegateContextMenu {
|
||||
onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText)
|
||||
},
|
||||
DelegateContextMenu.ReportMessageAction {},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Show User")
|
||||
icon.name: "username-copy"
|
||||
onTriggered: {
|
||||
RoomManager.resolveResource(author.id)
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ShowUserAction {},
|
||||
DelegateContextMenu.ViewSourceAction {},
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy Link")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
@@ -12,10 +12,11 @@ import org.kde.neochat
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
property string userId
|
||||
required property string placeholder
|
||||
required property string actionText
|
||||
required property string icon
|
||||
|
||||
title: i18n("Ban User")
|
||||
signal accepted(reason: string)
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
@@ -24,7 +25,7 @@ Kirigami.Page {
|
||||
|
||||
QQC2.TextArea {
|
||||
id: reason
|
||||
placeholderText: i18n("Reason for banning this user")
|
||||
placeholderText: root.placeholder
|
||||
anchors.fill: parent
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
@@ -40,11 +41,11 @@ Kirigami.Page {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban")
|
||||
icon.name: "im-ban-user"
|
||||
text: root.actionText
|
||||
icon.name: root.icon
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
root.room.ban(root.userId, reason.text);
|
||||
root.accepted(reason.text);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ Kirigami.Dialog {
|
||||
spacing: Kirigami.Units.largeSpacing * 4
|
||||
Avatar {
|
||||
source: root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar)
|
||||
name: SpaceHierarchyCache.recommendedSpaceDisplayName
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
property string eventId
|
||||
|
||||
property string userId: ""
|
||||
|
||||
title: userId.length > 0 ? i18n("Remove Messages") : i18n("Remove Message")
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
QQC2.TextArea {
|
||||
id: reason
|
||||
placeholderText: userId.length > 0 ? i18n("Reason for removing this user's recent messages") : i18n("Reason for removing this message")
|
||||
anchors.fill: parent
|
||||
wrapMode: TextEdit.Wrap
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
QQC2.DialogButtonBox {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove")
|
||||
icon.name: "delete"
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
if (root.userId.length > 0) {
|
||||
root.room.deleteMessagesByUser(root.userId, reason.text);
|
||||
} else {
|
||||
root.room.redactEvent(root.eventId, reason.text);
|
||||
}
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action", "Cancel")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
||||
onClicked: root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
property string eventId
|
||||
|
||||
title: i18n("Report Message")
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
QQC2.TextArea {
|
||||
id: reason
|
||||
placeholderText: i18n("Reason for reporting this message")
|
||||
anchors.fill: parent
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
QQC2.DialogButtonBox {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
root.room.reportEvent(eventId, reason.text);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action", "Cancel")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
||||
onClicked: root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ Delegates.RoundedItemDelegate {
|
||||
text: i18n("Configure room")
|
||||
display: QQC2.Button.IconOnly
|
||||
|
||||
icon.name: "configure"
|
||||
icon.name: "overflow-menu-symbolic"
|
||||
onClicked: createRoomListContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,15 +281,27 @@ Kirigami.Page {
|
||||
}
|
||||
|
||||
function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo, isThread) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
if (Kirigami.Settings.isMobile) {
|
||||
const contextMenu = fileDelegateContextMenuMobile.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
} else {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
}
|
||||
|
||||
function onShowMaximizedMedia(index) {
|
||||
@@ -327,6 +339,13 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenuMobile
|
||||
FileDelegateContextMenuMobile {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: maximizeComponent
|
||||
NeochatMaximizeComponent {
|
||||
|
||||
@@ -119,10 +119,7 @@ QQC2.Control {
|
||||
activeFocusOnTab: true
|
||||
|
||||
checked: RoomManager.currentSpace.length === 0
|
||||
onSelected: {
|
||||
RoomManager.currentSpace = "";
|
||||
root.selectionChanged();
|
||||
}
|
||||
onSelected: RoomManager.currentSpace = ""
|
||||
}
|
||||
AvatarTabButton {
|
||||
id: directChatButton
|
||||
@@ -154,7 +151,7 @@ QQC2.Control {
|
||||
visible: true
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
Kirigami.Theme.inherit: false
|
||||
color: root.connection.directChatsHaveHighlightNotifications ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||
color: root.connection.directChatsHaveHighlightNotifications || root.connection.directChatInvites ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||
radius: height / 2
|
||||
}
|
||||
|
||||
@@ -162,16 +159,20 @@ QQC2.Control {
|
||||
id: directChatNotificationCountTextMetrics
|
||||
text: directChatNotificationCountLabel.text
|
||||
}
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
|
||||
source: "list-add-symbolic"
|
||||
visible: root.connection.directChatInvites && root.connection.directChatNotifications === 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
checked: RoomManager.currentSpace === "DM"
|
||||
onSelected: {
|
||||
RoomManager.currentSpace = "DM";
|
||||
root.selectionChanged();
|
||||
}
|
||||
onSelected: RoomManager.currentSpace = "DM"
|
||||
}
|
||||
|
||||
Repeater {
|
||||
|
||||
@@ -122,7 +122,18 @@ Kirigami.Dialog {
|
||||
text: i18n("Kick this user")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: {
|
||||
root.room.kickMember(root.user.id);
|
||||
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Kick User"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
|
||||
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
|
||||
icon: "im-kick-user"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Kick User"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
root.room.kickMember(root.user.id, reason);
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
@@ -150,13 +161,18 @@ Kirigami.Dialog {
|
||||
icon.name: "im-ban-user"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
(root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'BanSheet'), {
|
||||
room: root.room,
|
||||
userId: root.user.id
|
||||
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Ban User"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
|
||||
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
|
||||
icon: "im-ban-user"
|
||||
}, {
|
||||
title: i18nc("@title", "Ban User"),
|
||||
title: i18nc("@title:dialog", "Ban User"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
root.room.ban(root.user.id, reason);
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
@@ -204,17 +220,22 @@ Kirigami.Dialog {
|
||||
visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact"))
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Remove recent messages by this user")
|
||||
text: i18nc("@action:button", "Remove recent messages by this user")
|
||||
icon.name: "delete"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
|
||||
room: root.room,
|
||||
userId: root.user.id
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Messages"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title", "Remove Messages"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
root.room.deleteMessagesByUser(root.user.id, reason);
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ void RoomManager::knockRoom(NeoChatConnection *account, const QString &roomAlias
|
||||
account,
|
||||
&NeoChatConnection::newRoom,
|
||||
this,
|
||||
[this](Quotient::Room *room) {
|
||||
[](Quotient::Room *room) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18n("You requested to join '%1'", room -> name()));
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
@@ -217,12 +217,12 @@ FormCard.FormCardPage {
|
||||
visible: colorSchemeDelegate.visible
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: colorSchemeDelegate
|
||||
visible: item !== null
|
||||
sourceComponent: Qt.createComponent('org.kde.neochat.settings', 'ColorScheme')
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
// Loader {
|
||||
// id: colorSchemeDelegate
|
||||
// visible: item !== null
|
||||
// sourceComponent: Qt.createComponent('org.kde.neochat.settings', 'ColorScheme')
|
||||
// Layout.fillWidth: true
|
||||
// }
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
|
||||
@@ -45,7 +45,7 @@ FormCard.FormCardPage {
|
||||
id: minimizeDelegate
|
||||
text: i18n("Minimize to system tray on startup")
|
||||
checked: NeoChatConfig.minimizeToSystemTrayOnStartup
|
||||
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile
|
||||
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile && NeoChatConfig.systemTray
|
||||
enabled: NeoChatConfig.systemTray && !NeoChatConfig.isMinimizeToSystemTrayOnStartupImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.minimizeToSystemTrayOnStartup = checked;
|
||||
@@ -56,6 +56,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: minimizeDelegate
|
||||
below: automaticallyDelegate
|
||||
visible: minimizeDelegate.visible
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
@@ -193,23 +194,6 @@ FormCard.FormCardPage {
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: showAvatarChangeDelegate
|
||||
below: hideImagesDelegate
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: hideImagesDelegate
|
||||
text: i18nc("@label:checkbox", "Hide image and video events by default")
|
||||
description: i18nc("@info", "When this option is enabled, images and videos are only shown after a button is clicked.")
|
||||
checked: NeoChatConfig.hideImages
|
||||
enabled: !NeoChatConfig.isHideImagesImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.hideImages = checked;
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("Chat Editor", "Editor")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -15,7 +16,7 @@ FormCard.FormCardPage {
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
title: i18nc("@title", "Security")
|
||||
title: i18nc("@title", "Security & Safety")
|
||||
|
||||
header: KirigamiComponents.Banner {
|
||||
id: banner
|
||||
@@ -24,13 +25,39 @@ FormCard.FormCardPage {
|
||||
type: Kirigami.MessageType.Error
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title:group", "Invitations")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
FormCard.FormButtonDelegate {
|
||||
id: ignoredUsersDelegate
|
||||
text: i18nc("@action:button", "Ignored Users")
|
||||
icon.name: "im-invisible-user"
|
||||
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
|
||||
title: i18nc("@title:window", "Ignored Users")
|
||||
});
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: ignoredUsersDelegate
|
||||
below: hideImagesDelegate
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: hideImagesDelegate
|
||||
text: i18nc("@label:checkbox", "Hide images and videos by default")
|
||||
description: i18nc("@info", "When this option is enabled, images and videos are only shown after a button is clicked.")
|
||||
checked: NeoChatConfig.hideImages
|
||||
enabled: !NeoChatConfig.isHideImagesImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.hideImages = checked;
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: hideImagesDelegate
|
||||
below: rejectInvitationsDelegate
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: rejectInvitationsDelegate
|
||||
text: i18nc("@option:check", "Reject invitations from unknown users")
|
||||
description: connection.canCheckMutualRooms ? i18n("If enabled, NeoChat will reject invitations from users you don't share a room with.") : i18n("Your server does not support this setting.")
|
||||
description: connection.canCheckMutualRooms ? i18nc("@info", "If enabled, NeoChat will reject invitations from users you don't share a room with.") : i18nc("@info", "Your server does not support this setting.")
|
||||
checked: NeoChatConfig.rejectUnknownInvites
|
||||
enabled: !NeoChatConfig.isRejectUnknownInvitesImmutable && connection.canCheckMutualRooms
|
||||
onToggled: {
|
||||
@@ -40,42 +67,15 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title:group", "Ignored Users")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Manage ignored users")
|
||||
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
|
||||
title: i18nc("@title:window", "Ignored Users")
|
||||
});
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Keys")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.deviceKey
|
||||
description: i18n("Device key")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.encryptionKey
|
||||
description: i18n("Encryption key")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.deviceId
|
||||
description: i18n("Device id")
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Encryption")
|
||||
visible: Controller.csSupported
|
||||
title: i18nc("@title", "Encryption Keys")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
visible: Controller.csSupported
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Import Encryption Keys")
|
||||
id: importKeysDelegate
|
||||
text: i18nc("@action:button", "Import Keys")
|
||||
description: i18nc("@info", "Import encryption keys from a backup.")
|
||||
icon.name: "document-import"
|
||||
onClicked: {
|
||||
let dialog = root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "ImportKeysDialog"), {
|
||||
@@ -91,8 +91,14 @@ FormCard.FormCardPage {
|
||||
banner.visible = false;
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: importKeysDelegate
|
||||
below: exportKeysDelegate
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Export Encryption Keys")
|
||||
id: exportKeysDelegate
|
||||
text: i18nc("@action:button", "Export Keys")
|
||||
description: i18nc("@info", "Export this device's encryption keys.")
|
||||
icon.name: "document-export"
|
||||
onClicked: {
|
||||
root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "ExportKeysDialog"), {
|
||||
|
||||
@@ -48,7 +48,7 @@ KirigamiSettings.ConfigurationView {
|
||||
},
|
||||
KirigamiSettings.ConfigurationModule {
|
||||
moduleId: "security"
|
||||
text: i18n("Security")
|
||||
text: i18nc("@title", "Security & Safety")
|
||||
icon.name: "preferences-security"
|
||||
page: () => Qt.createComponent("org.kde.neochat.settings", "NeoChatSecurityPage")
|
||||
initialProperties: () => {
|
||||
|
||||
@@ -459,7 +459,7 @@ QString TextHandler::cleanAttributes(const QString &tag, const QString &tagStrin
|
||||
nextAttributeIndex += 1;
|
||||
|
||||
while (nextAttributeIndex < tagString.length()) {
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endTagType, nextAttributeIndex);
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endAttributeType, nextAttributeIndex);
|
||||
if (nextSpaceIndex == -1) {
|
||||
nextSpaceIndex = tagString.length();
|
||||
}
|
||||
@@ -505,7 +505,7 @@ QVariantMap TextHandler::getAttributes(const QString &tag, const QString &tagStr
|
||||
nextAttributeIndex += 1;
|
||||
|
||||
while (nextAttributeIndex < tagString.length()) {
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endTagType, nextAttributeIndex);
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endAttributeType, nextAttributeIndex);
|
||||
if (nextSpaceIndex == -1) {
|
||||
nextSpaceIndex = tagString.length();
|
||||
}
|
||||
@@ -626,7 +626,7 @@ QString TextHandler::linkifyUrls(QString stringIn)
|
||||
int skip = 0;
|
||||
if (match.captured(0).size() > 0) {
|
||||
if (stringIn.left(index).count(QStringLiteral("<code>")) == stringIn.left(index).count(QStringLiteral("</code>"))) {
|
||||
auto replacement = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%1</a>").arg(match.captured(2));
|
||||
auto replacement = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%1</a>").arg(match.captured(1));
|
||||
stringIn = stringIn.replace(index, match.captured(0).size(), replacement);
|
||||
} else {
|
||||
skip = match.captured().length();
|
||||
|
||||
@@ -25,16 +25,6 @@ TextEdit {
|
||||
*/
|
||||
property bool isReply: false
|
||||
|
||||
/**
|
||||
* @brief Regex for detecting a message with a single emoji.
|
||||
*/
|
||||
readonly property var isEmojiRegex: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
||||
|
||||
/**
|
||||
* @brief Whether the message is an emoji
|
||||
*/
|
||||
readonly property var isEmoji: isEmojiRegex.test(display)
|
||||
|
||||
/**
|
||||
* @brief Regex for detecting a message with a spoiler.
|
||||
*/
|
||||
@@ -113,8 +103,8 @@ a{
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
font {
|
||||
pointSize: !root.isReply && root.isEmoji ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
family: root.isEmoji ? 'emoji' : Kirigami.Theme.defaultFont.family
|
||||
pointSize: !root.isReply && QmlUtils.isEmoji(display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
family: QmlUtils.isEmoji(display) ? 'emoji' : Kirigami.Theme.defaultFont.family
|
||||
}
|
||||
selectByMouse: !Kirigami.Settings.isMobile
|
||||
readOnly: true
|
||||
|
||||
@@ -3,12 +3,24 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef HAVE_ICU
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextCharFormat>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/urename.h>
|
||||
#endif
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
bool QmlUtils::isEmoji(const QString &text)
|
||||
{
|
||||
return Utils::isEmoji(text);
|
||||
}
|
||||
|
||||
bool QmlUtils::isValidJson(const QByteArray &json)
|
||||
{
|
||||
return !QJsonDocument::fromJson(json).isNull();
|
||||
@@ -26,4 +38,28 @@ QColor QmlUtils::getUserColor(qreal hueF)
|
||||
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
|
||||
}
|
||||
|
||||
bool Utils::isEmoji(const QString &text)
|
||||
{
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "moc_utils.cpp"
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
return _instance;
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isEmoji(const QString &text);
|
||||
Q_INVOKABLE bool isValidJson(const QByteArray &json);
|
||||
Q_INVOKABLE QString escapeString(const QString &string);
|
||||
Q_INVOKABLE QColor getUserColor(qreal hueF);
|
||||
@@ -53,11 +54,14 @@ inline QColor getUserColor(qreal hueF)
|
||||
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
|
||||
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
|
||||
}
|
||||
|
||||
bool isEmoji(const QString &text);
|
||||
}
|
||||
|
||||
namespace TextRegex
|
||||
{
|
||||
static const QRegularExpression endTagType{QStringLiteral("(>| )")};
|
||||
static const QRegularExpression endTagType{QStringLiteral("[> /]")};
|
||||
static const QRegularExpression endAttributeType{QStringLiteral("[> ]")};
|
||||
static const QRegularExpression attributeData{QStringLiteral("['\"](.*?)['\"]")};
|
||||
static const QRegularExpression removeReply{QStringLiteral("> <.*?>.*?\\n\\n"), QRegularExpression::DotMatchesEverythingOption};
|
||||
static const QRegularExpression removeRichReply{QStringLiteral("<mx-reply>.*?</mx-reply>"), QRegularExpression::DotMatchesEverythingOption};
|
||||
@@ -76,6 +80,6 @@ static const QRegularExpression
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
static const QRegularExpression emailAddress(QStringLiteral(R"(<a.*?<\/a>(*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
|
||||
static const QRegularExpression mxId(QStringLiteral(R"((?<=^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
}
|
||||
|
||||
@@ -40,26 +40,6 @@ QWindow *WindowController::window() const
|
||||
return m_window;
|
||||
}
|
||||
|
||||
void WindowController::restoreGeometry()
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
const KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
|
||||
KWindowConfig::restoreWindowSize(m_window, windowGroup);
|
||||
KWindowConfig::restoreWindowPosition(m_window, windowGroup);
|
||||
}
|
||||
|
||||
void WindowController::saveGeometry()
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
|
||||
KWindowConfig::saveWindowPosition(m_window, windowGroup);
|
||||
KWindowConfig::saveWindowSize(m_window, windowGroup);
|
||||
|
||||
stateConfig->sync();
|
||||
}
|
||||
|
||||
void WindowController::showAndRaiseWindow(const QString &startupId)
|
||||
{
|
||||
if (m_window == nullptr) {
|
||||
@@ -67,7 +47,6 @@ void WindowController::showAndRaiseWindow(const QString &startupId)
|
||||
}
|
||||
if (!m_window->isVisible()) {
|
||||
m_window->show();
|
||||
restoreGeometry();
|
||||
}
|
||||
|
||||
#ifdef HAVE_WINDOWSYSTEM
|
||||
|
||||
@@ -41,16 +41,6 @@ public:
|
||||
*/
|
||||
QWindow *window() const;
|
||||
|
||||
/**
|
||||
* @brief Restore any saved window geometry if available.
|
||||
*/
|
||||
void restoreGeometry();
|
||||
|
||||
/**
|
||||
* @brief Save the current window geometry.
|
||||
*/
|
||||
Q_INVOKABLE void saveGeometry();
|
||||
|
||||
/**
|
||||
* @brief Show the window and raise to the top.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user