diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 7d964bb5..b27128e0 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -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_,
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index 80ad7f47..acafd964 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -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_;
@@ -288,8 +301,11 @@ private:
std::deque<QString> history_;
std::size_t history_index_ = 0;
int selectionStart = 0, selectionEnd = 0, cursorPosition = 0;
- bool uploading_ = false;
- bool containsAtRoom_ = false;
+ 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;
|