mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-24 03:58:49 +03:00
Merge pull request #1388 from Nheko-Reborn/command
Warn if an invalid command is entered
This commit is contained in:
commit
7d8ccd4ce8
7 changed files with 176 additions and 66 deletions
|
@ -14,6 +14,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
|
||||
|
|
47
resources/qml/MessageInputWarning.qml
Normal file
47
resources/qml/MessageInputWarning.qml
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -153,7 +153,20 @@ Item {
|
|||
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 {
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
<file>qml/ForwardCompleter.qml</file>
|
||||
<file>qml/SelfVerificationCheck.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/AdaptiveLayoutElement.qml</file>
|
||||
<file>qml/components/AvatarListTile.qml</file>
|
||||
|
|
|
@ -224,8 +224,9 @@ InputBar::insertMimeData(const QMimeData *md)
|
|||
}
|
||||
|
||||
void
|
||||
InputBar::updateAtRoom(const QString &t)
|
||||
InputBar::updateTextContentProperties(const QString &t)
|
||||
{
|
||||
// check for @room
|
||||
bool roomMention = false;
|
||||
|
||||
if (t.size() > 4) {
|
||||
|
@ -249,6 +250,61 @@ InputBar::updateAtRoom(const QString &t)
|
|||
this->containsAtRoom_ = roomMention;
|
||||
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
|
||||
|
@ -263,7 +319,7 @@ InputBar::setText(const QString &newText)
|
|||
if (history_.size() == INPUT_HISTORY_SIZE)
|
||||
history_.pop_back();
|
||||
|
||||
updateAtRoom(QLatin1String(""));
|
||||
updateTextContentProperties(QLatin1String(""));
|
||||
emit textChanged(newText);
|
||||
}
|
||||
void
|
||||
|
@ -284,7 +340,7 @@ InputBar::updateState(int selectionStart_,
|
|||
history_.front() = text_;
|
||||
history_index_ = 0;
|
||||
|
||||
updateAtRoom(text_);
|
||||
updateTextContentProperties(text_);
|
||||
// disabled, as it moves the cursor to the end
|
||||
// emit textChanged(text_);
|
||||
}
|
||||
|
@ -312,7 +368,7 @@ InputBar::previousText()
|
|||
else if (text().isEmpty())
|
||||
history_index_--;
|
||||
|
||||
updateAtRoom(text());
|
||||
updateTextContentProperties(text());
|
||||
return text();
|
||||
}
|
||||
|
||||
|
@ -323,7 +379,7 @@ InputBar::nextText()
|
|||
if (history_index_ >= INPUT_HISTORY_SIZE)
|
||||
history_index_ = 0;
|
||||
|
||||
updateAtRoom(text());
|
||||
updateTextContentProperties(text());
|
||||
return text();
|
||||
}
|
||||
|
||||
|
@ -341,20 +397,12 @@ InputBar::send()
|
|||
|
||||
auto wasEdit = !room->edit().isEmpty();
|
||||
|
||||
if (text().startsWith('/')) {
|
||||
int command_end = text().indexOf(QRegularExpression(QStringLiteral("\\s")));
|
||||
if (command_end == -1)
|
||||
command_end = text().size();
|
||||
auto name = text().mid(1, command_end - 1);
|
||||
auto args = text().mid(command_end + 1);
|
||||
if (name.isEmpty() || name == QLatin1String("/")) {
|
||||
message(args);
|
||||
} else {
|
||||
command(name, args);
|
||||
}
|
||||
} else {
|
||||
auto [commandName, args] = getCommandAndArgs();
|
||||
updateTextContentProperties(text());
|
||||
if (containsIncompleteCommand_)
|
||||
return;
|
||||
if (commandName.isEmpty() || !command(commandName, args))
|
||||
message(text());
|
||||
}
|
||||
|
||||
if (!wasEdit) {
|
||||
history_.push_front(QLatin1String(""));
|
||||
|
@ -716,6 +764,24 @@ InputBar::video(const QString &filename,
|
|||
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
||||
}
|
||||
|
||||
QPair<QString, QString>
|
||||
InputBar::getCommandAndArgs(const QString ¤tText) 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
|
||||
InputBar::sticker(CombinedImagePackModel *model, int row)
|
||||
{
|
||||
|
@ -741,7 +807,7 @@ InputBar::sticker(CombinedImagePackModel *model, int row)
|
|||
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
InputBar::command(const QString &command, QString args)
|
||||
{
|
||||
if (command == QLatin1String("me")) {
|
||||
|
@ -829,16 +895,16 @@ InputBar::command(const QString &command, QString args)
|
|||
// 1 - Going directly to a given event ID
|
||||
if (args[0] == '$') {
|
||||
room->showEvent(args);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// 2 - Going directly to a given message index
|
||||
if (args[0] >= '0' && args[0] <= '9') {
|
||||
room->showEvent(args);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// 3 - Matrix URI handler, as if you clicked the URI
|
||||
if (ChatPage::instance()->handleMatrixUri(args)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
|
||||
} else if (command == QLatin1String("converttodm")) {
|
||||
|
@ -846,7 +912,11 @@ 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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MediaUpload::MediaUpload(std::unique_ptr<QIODevice> source_,
|
||||
|
|
|
@ -173,6 +173,11 @@ class InputBar final : public QObject
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
|
||||
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(QVariantList uploads READ uploads NOTIFY uploadsChanged)
|
||||
|
||||
|
@ -198,6 +203,9 @@ public slots:
|
|||
void setText(const QString &newText);
|
||||
|
||||
[[nodiscard]] bool containsAtRoom() const { return containsAtRoom_; }
|
||||
bool containsInvalidCommand() const { return containsInvalidCommand_; }
|
||||
bool containsIncompleteCommand() const { return containsIncompleteCommand_; }
|
||||
QString currentCommand() const { return currentCommand_; }
|
||||
|
||||
void send();
|
||||
bool tryPasteAttachment(bool fromMouse);
|
||||
|
@ -225,13 +233,16 @@ signals:
|
|||
void textChanged(QString newText);
|
||||
void uploadingChanged(bool value);
|
||||
void containsAtRoomChanged();
|
||||
void containsInvalidCommandChanged();
|
||||
void containsIncompleteCommandChanged();
|
||||
void currentCommandChanged();
|
||||
void uploadsChanged();
|
||||
|
||||
private:
|
||||
void emote(const QString &body, bool rainbowify);
|
||||
void notice(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,
|
||||
const std::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
|
@ -267,6 +278,8 @@ private:
|
|||
const QSize &thumbnailDimensions,
|
||||
const QString &blurhash);
|
||||
|
||||
QPair<QString, QString> getCommandAndArgs() const { return getCommandAndArgs(text()); }
|
||||
QPair<QString, QString> getCommandAndArgs(const QString ¤tText) const;
|
||||
mtx::common::Relations generateRelations() const;
|
||||
|
||||
void startUploadFromPath(const QString &path);
|
||||
|
@ -280,7 +293,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void updateAtRoom(const QString &t);
|
||||
void updateTextContentProperties(const QString &t);
|
||||
|
||||
QTimer typingRefresh_;
|
||||
QTimer typingTimeout_;
|
||||
|
@ -290,6 +303,9 @@ private:
|
|||
int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
|
||||
bool uploading_ = false;
|
||||
bool containsAtRoom_ = false;
|
||||
bool containsInvalidCommand_ = false;
|
||||
bool containsIncompleteCommand_ = false;
|
||||
QString currentCommand_;
|
||||
|
||||
using UploadHandle = std::unique_ptr<MediaUpload, DeleteLaterDeleter>;
|
||||
std::vector<UploadHandle> unconfirmedUploads;
|
||||
|
|
Loading…
Reference in a new issue