From b6ef00b5ee14bd0adc85c3a98bb8a127f79932ea Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 23 Feb 2023 21:57:53 -0500 Subject: [PATCH] Show warning when invalid command is entered Fixes #1363 Please note that this doesn't prompt when you try to send a message with a bad command. --- resources/qml/MessageInput.qml | 2 + ...ionWarning.qml => MessageInputWarning.qml} | 12 +++- resources/qml/TimelineView.qml | 19 +++++- resources/res.qrc | 2 +- src/CompletionProxyModel.h | 2 + src/timeline/InputBar.cpp | 2 + src/ui/NhekoGlobalObject.cpp | 60 +++++++++++++++++++ src/ui/NhekoGlobalObject.h | 3 + 8 files changed, 97 insertions(+), 5 deletions(-) rename resources/qml/{NotificationWarning.qml => MessageInputWarning.qml} (73%) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index f31123e5..f6fe03c5 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -13,6 +13,8 @@ import im.nheko 1.0 Rectangle { id: inputBar + readonly property string text: messageInput.text + color: Nheko.colors.window Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight diff --git a/resources/qml/NotificationWarning.qml b/resources/qml/MessageInputWarning.qml similarity index 73% rename from resources/qml/NotificationWarning.qml rename to resources/qml/MessageInputWarning.qml index 13d2cb23..c2db2daa 100644 --- a/resources/qml/NotificationWarning.qml +++ b/resources/qml/MessageInputWarning.qml @@ -8,14 +8,20 @@ import QtQuick.Layouts 1.2 import im.nheko 1.0 Item { - implicitHeight: warningRect.visible ? warningDisplay.implicitHeight + 2 * Nheko.paddingSmall : 0 + id: warningRoot + + required property string text + required property bool isVisible + + implicitHeight: isVisible ? warningDisplay.implicitHeight + 2 * Nheko.paddingSmall : 0 height: implicitHeight Layout.fillWidth: true + Layout.margins: isVisible ? Nheko.paddingSmall : 0 Rectangle { id: warningRect - visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom) + visible: warningRoot.isVisible // TODO: Qt.alpha() would make more sense but it wasn't working... color: Qt.rgba(Nheko.theme.error.r, Nheko.theme.error.g, Nheko.theme.error.b, 0.3) border.width: 1 @@ -31,7 +37,7 @@ Item { anchors.verticalCenter: parent.verticalCenter anchors.margins: Nheko.paddingSmall color: Nheko.colors.text - text: qsTr("You are about to notify the whole room") + text: warningRoot.text textFormat: Text.PlainText } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e836f60f..5c982270 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -153,13 +153,30 @@ Item { UploadBox { } - NotificationWarning { + MessageInputWarning { + text: qsTr("You are about to notify the whole room") + isVisible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom) + } + + MessageInputWarning { + text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(Nheko.getCommandFromText(input.text)) + isVisible: { + if (!input.text) + return false; + + let command = Nheko.getCommandFromText(input.text); + if (Nheko.isInvalidCommand(command) && ("/" + command !== input.text)) + return true; + else + return false; + } } ReplyPopup { } MessageInput { + id: input } } diff --git a/resources/res.qrc b/resources/res.qrc index 88159d40..5fbd00d7 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -123,7 +123,7 @@ qml/ForwardCompleter.qml qml/SelfVerificationCheck.qml qml/TypingIndicator.qml - qml/NotificationWarning.qml + qml/MessageInputWarning.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/AvatarListTile.qml diff --git a/src/CompletionProxyModel.h b/src/CompletionProxyModel.h index e0f00788..90daf7ad 100644 --- a/src/CompletionProxyModel.h +++ b/src/CompletionProxyModel.h @@ -184,6 +184,8 @@ public slots: void setSearchString(const QString &s); QString searchString() const { return searchString_; } + bool hasCompletion() const { return rowCount() > 0; } + signals: void newSearchString(QString); diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 7d964bb5..5184ba94 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -846,6 +846,8 @@ InputBar::command(const QString &command, QString args) cache::getMembers(this->room->roomId().toStdString(), 0, -1)); } else if (command == QLatin1String("converttoroom")) { utils::removeDirectFromRoom(this->room->roomId()); + } else { + message("/" + command + " " + args); } } diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index a6f9abe7..3cb6a8a8 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -190,3 +190,63 @@ Nheko::setWindowRole([[maybe_unused]] QWindow *win, [[maybe_unused]] QString new QXcbWindowFunctions::setWmWindowRole(win, newRole.toUtf8()); #endif } + +QString +Nheko::getCommandFromText(const QString &text) +{ + if (text.startsWith('/')) { + int command_end = text.indexOf(QRegularExpression(QStringLiteral("\\s"))); + if (command_end == -1) + command_end = text.size(); + auto command = text.mid(1, command_end - 1); + if (command.isEmpty() || command == QLatin1String("/")) + return {}; + else { + return command; + } + } else + return {}; +} + +bool +Nheko::isInvalidCommand(QString command) const +{ + if (command.size() <= 0) + return false; + + static const QStringList validCommands{QStringLiteral("/me"), + QStringLiteral("/react"), + QStringLiteral("/join"), + QStringLiteral("/knock"), + QStringLiteral("/part"), + QStringLiteral("/leave"), + QStringLiteral("/invite"), + QStringLiteral("/kick"), + QStringLiteral("/ban"), + QStringLiteral("/unban"), + QStringLiteral("/redact"), + QStringLiteral("/roomnick"), + QStringLiteral("/shrug"), + QStringLiteral("/fliptable"), + QStringLiteral("/unfliptable"), + QStringLiteral("/sovietflip"), + QStringLiteral("/clear-timeline"), + QStringLiteral("/reset-state"), + QStringLiteral("/rotate-megolm-session"), + QStringLiteral("/md"), + QStringLiteral("/cmark"), + QStringLiteral("/plain"), + QStringLiteral("/rainbow"), + QStringLiteral("/rainbowme"), + QStringLiteral("/notice"), + QStringLiteral("/rainbownotice"), + QStringLiteral("/confetti"), + QStringLiteral("/rainbowconfetti"), + QStringLiteral("/goto"), + QStringLiteral("/converttodm"), + QStringLiteral("/converttoroom")}; + + if (!command.startsWith('/')) + command.prepend('/'); + return !validCommands.contains(command); +} diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h index b7a7a637..1ea2c109 100644 --- a/src/ui/NhekoGlobalObject.h +++ b/src/ui/NhekoGlobalObject.h @@ -72,6 +72,9 @@ public: Q_INVOKABLE void setTransientParent(QWindow *window, QWindow *parentWindow) const; Q_INVOKABLE void setWindowRole(QWindow *win, QString newRole) const; + Q_INVOKABLE QString getCommandFromText(const QString &text); + Q_INVOKABLE bool isInvalidCommand(QString command) const; + public slots: void updateUserProfile();