Merge pull request #1388 from Nheko-Reborn/command

Warn if an invalid command is entered
This commit is contained in:
DeepBlueV7.X 2023-03-10 00:17:06 +00:00 committed by GitHub
commit 7d8ccd4ce8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 176 additions and 66 deletions

View file

@ -14,6 +14,8 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: inputBar id: inputBar
readonly property string text: messageInput.text
color: Nheko.colors.window color: Nheko.colors.window
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight Layout.preferredHeight: row.implicitHeight

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import im.nheko 1.0
Rectangle {
id: warningRoot
required property string text
property color bubbleColor: Nheko.theme.error
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
height: implicitHeight
Layout.fillWidth: true
color: Nheko.colors.window // required to hide the timeline behind this warning
Rectangle {
id: warningRect
visible: warningRoot.visible
// TODO: Qt.alpha() would make more sense but it wasn't working...
color: Qt.rgba(bubbleColor.r, bubbleColor.g, bubbleColor.b, 0.3)
border.width: 1
border.color: bubbleColor
radius: 3
anchors.fill: parent
anchors.margins: visible ? Nheko.paddingSmall : 0
z: 3
Label {
id: warningDisplay
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Nheko.paddingSmall
color: Nheko.colors.text
text: warningRoot.text
textFormat: Text.PlainText
}
}
}

View file

@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import im.nheko 1.0
Item {
implicitHeight: warningRect.visible ? warningDisplay.implicitHeight : 0
height: implicitHeight
Layout.fillWidth: true
Rectangle {
id: warningRect
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
color: Nheko.colors.base
anchors.fill: parent
z: 3
Label {
id: warningDisplay
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
anchors.bottom: parent.bottom
color: Nheko.theme.red
text: qsTr("You are about to notify the whole room")
textFormat: Text.PlainText
}
}
}

View file

@ -153,7 +153,20 @@ Item {
UploadBox { UploadBox {
} }
NotificationWarning { MessageInputWarning {
text: qsTr("You are about to notify the whole room")
visible: (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(room ? room.input.currentCommand : "")
visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false
}
MessageInputWarning {
text: qsTr("/%1 looks like an incomplete command. To send it anyway, add a space to the end of your message.").arg(room ? room.input.currentCommand : "")
visible: room ? room.input.containsIncompleteCommand : false
bubbleColor: Nheko.theme.orange
} }
ReplyPopup { ReplyPopup {

View file

@ -122,7 +122,7 @@
<file>qml/ForwardCompleter.qml</file> <file>qml/ForwardCompleter.qml</file>
<file>qml/SelfVerificationCheck.qml</file> <file>qml/SelfVerificationCheck.qml</file>
<file>qml/TypingIndicator.qml</file> <file>qml/TypingIndicator.qml</file>
<file>qml/NotificationWarning.qml</file> <file>qml/MessageInputWarning.qml</file>
<file>qml/components/AdaptiveLayout.qml</file> <file>qml/components/AdaptiveLayout.qml</file>
<file>qml/components/AdaptiveLayoutElement.qml</file> <file>qml/components/AdaptiveLayoutElement.qml</file>
<file>qml/components/AvatarListTile.qml</file> <file>qml/components/AvatarListTile.qml</file>

View file

@ -224,8 +224,9 @@ InputBar::insertMimeData(const QMimeData *md)
} }
void void
InputBar::updateAtRoom(const QString &t) InputBar::updateTextContentProperties(const QString &t)
{ {
// check for @room
bool roomMention = false; bool roomMention = false;
if (t.size() > 4) { if (t.size() > 4) {
@ -249,6 +250,61 @@ InputBar::updateAtRoom(const QString &t)
this->containsAtRoom_ = roomMention; this->containsAtRoom_ = roomMention;
emit containsAtRoomChanged(); emit containsAtRoomChanged();
} }
// check for invalid commands
auto commandName = getCommandAndArgs(t).first;
static const QSet<QString> 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")};
bool hasInvalidCommand = !commandName.isNull() && !validCommands.contains(commandName);
bool hasIncompleteCommand = hasInvalidCommand && '/' + commandName == t;
bool signalsChanged{false};
if (containsInvalidCommand_ != hasInvalidCommand) {
containsInvalidCommand_ = hasInvalidCommand;
signalsChanged = true;
}
if (containsIncompleteCommand_ != hasIncompleteCommand) {
containsIncompleteCommand_ = hasIncompleteCommand;
signalsChanged = true;
}
if (currentCommand_ != commandName) {
currentCommand_ = commandName;
signalsChanged = true;
}
if (signalsChanged) {
emit currentCommandChanged();
emit containsInvalidCommandChanged();
emit containsIncompleteCommandChanged();
}
} }
void void
@ -263,7 +319,7 @@ InputBar::setText(const QString &newText)
if (history_.size() == INPUT_HISTORY_SIZE) if (history_.size() == INPUT_HISTORY_SIZE)
history_.pop_back(); history_.pop_back();
updateAtRoom(QLatin1String("")); updateTextContentProperties(QLatin1String(""));
emit textChanged(newText); emit textChanged(newText);
} }
void void
@ -284,7 +340,7 @@ InputBar::updateState(int selectionStart_,
history_.front() = text_; history_.front() = text_;
history_index_ = 0; history_index_ = 0;
updateAtRoom(text_); updateTextContentProperties(text_);
// disabled, as it moves the cursor to the end // disabled, as it moves the cursor to the end
// emit textChanged(text_); // emit textChanged(text_);
} }
@ -312,7 +368,7 @@ InputBar::previousText()
else if (text().isEmpty()) else if (text().isEmpty())
history_index_--; history_index_--;
updateAtRoom(text()); updateTextContentProperties(text());
return text(); return text();
} }
@ -323,7 +379,7 @@ InputBar::nextText()
if (history_index_ >= INPUT_HISTORY_SIZE) if (history_index_ >= INPUT_HISTORY_SIZE)
history_index_ = 0; history_index_ = 0;
updateAtRoom(text()); updateTextContentProperties(text());
return text(); return text();
} }
@ -341,20 +397,12 @@ InputBar::send()
auto wasEdit = !room->edit().isEmpty(); auto wasEdit = !room->edit().isEmpty();
if (text().startsWith('/')) { auto [commandName, args] = getCommandAndArgs();
int command_end = text().indexOf(QRegularExpression(QStringLiteral("\\s"))); updateTextContentProperties(text());
if (command_end == -1) if (containsIncompleteCommand_)
command_end = text().size(); return;
auto name = text().mid(1, command_end - 1); if (commandName.isEmpty() || !command(commandName, args))
auto args = text().mid(command_end + 1);
if (name.isEmpty() || name == QLatin1String("/")) {
message(args);
} else {
command(name, args);
}
} else {
message(text()); message(text());
}
if (!wasEdit) { if (!wasEdit) {
history_.push_front(QLatin1String("")); history_.push_front(QLatin1String(""));
@ -716,6 +764,24 @@ InputBar::video(const QString &filename,
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage); room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
} }
QPair<QString, QString>
InputBar::getCommandAndArgs(const QString &currentText) const
{
if (!currentText.startsWith('/'))
return {{}, currentText};
int command_end = currentText.indexOf(QRegularExpression(QStringLiteral("\\s")));
if (command_end == -1)
command_end = currentText.size();
auto name = currentText.mid(1, command_end - 1);
auto args = currentText.mid(command_end + 1);
if (name.isEmpty() || name == QLatin1String("/")) {
return {{}, currentText};
} else {
return {name, args};
}
}
void void
InputBar::sticker(CombinedImagePackModel *model, int row) InputBar::sticker(CombinedImagePackModel *model, int row)
{ {
@ -741,7 +807,7 @@ InputBar::sticker(CombinedImagePackModel *model, int row)
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker); room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
} }
void bool
InputBar::command(const QString &command, QString args) InputBar::command(const QString &command, QString args)
{ {
if (command == QLatin1String("me")) { if (command == QLatin1String("me")) {
@ -829,16 +895,16 @@ InputBar::command(const QString &command, QString args)
// 1 - Going directly to a given event ID // 1 - Going directly to a given event ID
if (args[0] == '$') { if (args[0] == '$') {
room->showEvent(args); room->showEvent(args);
return; return true;
} }
// 2 - Going directly to a given message index // 2 - Going directly to a given message index
if (args[0] >= '0' && args[0] <= '9') { if (args[0] >= '0' && args[0] <= '9') {
room->showEvent(args); room->showEvent(args);
return; return true;
} }
// 3 - Matrix URI handler, as if you clicked the URI // 3 - Matrix URI handler, as if you clicked the URI
if (ChatPage::instance()->handleMatrixUri(args)) { if (ChatPage::instance()->handleMatrixUri(args)) {
return; return true;
} }
nhlog::net()->error("Could not resolve goto: {}", args.toStdString()); nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
} else if (command == QLatin1String("converttodm")) { } else if (command == QLatin1String("converttodm")) {
@ -846,7 +912,11 @@ InputBar::command(const QString &command, QString args)
cache::getMembers(this->room->roomId().toStdString(), 0, -1)); cache::getMembers(this->room->roomId().toStdString(), 0, -1));
} else if (command == QLatin1String("converttoroom")) { } else if (command == QLatin1String("converttoroom")) {
utils::removeDirectFromRoom(this->room->roomId()); utils::removeDirectFromRoom(this->room->roomId());
} else {
return false;
} }
return true;
} }
MediaUpload::MediaUpload(std::unique_ptr<QIODevice> source_, MediaUpload::MediaUpload(std::unique_ptr<QIODevice> source_,

View file

@ -173,6 +173,11 @@ class InputBar final : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged) Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged) Q_PROPERTY(bool containsAtRoom READ containsAtRoom NOTIFY containsAtRoomChanged)
Q_PROPERTY(
bool containsInvalidCommand READ containsInvalidCommand NOTIFY containsInvalidCommandChanged)
Q_PROPERTY(bool containsIncompleteCommand READ containsIncompleteCommand NOTIFY
containsIncompleteCommandChanged)
Q_PROPERTY(QString currentCommand READ currentCommand NOTIFY currentCommandChanged)
Q_PROPERTY(QString text READ text NOTIFY textChanged) Q_PROPERTY(QString text READ text NOTIFY textChanged)
Q_PROPERTY(QVariantList uploads READ uploads NOTIFY uploadsChanged) Q_PROPERTY(QVariantList uploads READ uploads NOTIFY uploadsChanged)
@ -198,6 +203,9 @@ public slots:
void setText(const QString &newText); void setText(const QString &newText);
[[nodiscard]] bool containsAtRoom() const { return containsAtRoom_; } [[nodiscard]] bool containsAtRoom() const { return containsAtRoom_; }
bool containsInvalidCommand() const { return containsInvalidCommand_; }
bool containsIncompleteCommand() const { return containsIncompleteCommand_; }
QString currentCommand() const { return currentCommand_; }
void send(); void send();
bool tryPasteAttachment(bool fromMouse); bool tryPasteAttachment(bool fromMouse);
@ -225,13 +233,16 @@ signals:
void textChanged(QString newText); void textChanged(QString newText);
void uploadingChanged(bool value); void uploadingChanged(bool value);
void containsAtRoomChanged(); void containsAtRoomChanged();
void containsInvalidCommandChanged();
void containsIncompleteCommandChanged();
void currentCommandChanged();
void uploadsChanged(); void uploadsChanged();
private: private:
void emote(const QString &body, bool rainbowify); void emote(const QString &body, bool rainbowify);
void notice(const QString &body, bool rainbowify); void notice(const QString &body, bool rainbowify);
void confetti(const QString &body, bool rainbowify); void confetti(const QString &body, bool rainbowify);
void command(const QString &name, QString args); bool command(const QString &name, QString args);
void image(const QString &filename, void image(const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
@ -267,6 +278,8 @@ private:
const QSize &thumbnailDimensions, const QSize &thumbnailDimensions,
const QString &blurhash); const QString &blurhash);
QPair<QString, QString> getCommandAndArgs() const { return getCommandAndArgs(text()); }
QPair<QString, QString> getCommandAndArgs(const QString &currentText) const;
mtx::common::Relations generateRelations() const; mtx::common::Relations generateRelations() const;
void startUploadFromPath(const QString &path); void startUploadFromPath(const QString &path);
@ -280,7 +293,7 @@ private:
} }
} }
void updateAtRoom(const QString &t); void updateTextContentProperties(const QString &t);
QTimer typingRefresh_; QTimer typingRefresh_;
QTimer typingTimeout_; QTimer typingTimeout_;
@ -288,8 +301,11 @@ private:
std::deque<QString> history_; std::deque<QString> history_;
std::size_t history_index_ = 0; std::size_t history_index_ = 0;
int selectionStart = 0, selectionEnd = 0, cursorPosition = 0; int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
bool uploading_ = false; bool uploading_ = false;
bool containsAtRoom_ = false; bool containsAtRoom_ = false;
bool containsInvalidCommand_ = false;
bool containsIncompleteCommand_ = false;
QString currentCommand_;
using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>; using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>;
std::vector<UploadHandle> unconfirmedUploads; std::vector<UploadHandle> unconfirmedUploads;