fix: disable reply options where we know it wouldn't work (#6119)
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
- Bugfix: Fixed a crash that could occur when an image started loading mid app shutdown. (#6213)
|
- Bugfix: Fixed a crash that could occur when an image started loading mid app shutdown. (#6213)
|
||||||
- Bugfix: Fixed some minor typos. (#6196)
|
- Bugfix: Fixed some minor typos. (#6196)
|
||||||
- Bugfix: Fixed inconsistent spaces in messages when using fractional scaling. (#6231, #6254)
|
- Bugfix: Fixed inconsistent spaces in messages when using fractional scaling. (#6231, #6254)
|
||||||
|
- Bugfix: Don't add reply buttons to messages that are invalid reply targets. (#6119)
|
||||||
- Dev: Mini refactor of Split. (#6148)
|
- Dev: Mini refactor of Split. (#6148)
|
||||||
- Dev: Conan will no longer generate a `CMakeUserPresets.json` file. (#6117)
|
- Dev: Conan will no longer generate a `CMakeUserPresets.json` file. (#6117)
|
||||||
- Dev: Pass `--force-openssl` when installing from CMake in Qt 6.8+. (#6129)
|
- Dev: Pass `--force-openssl` when installing from CMake in Qt 6.8+. (#6129)
|
||||||
|
|||||||
@@ -159,4 +159,44 @@ QJsonObject Message::toJson() const
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::ReplyStatus Message::isReplyable() const
|
||||||
|
{
|
||||||
|
if (this->loginName.isEmpty())
|
||||||
|
{
|
||||||
|
// no replies can happen
|
||||||
|
return ReplyStatus::NotReplyable;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int oneDayInSeconds = 24 * 60 * 60;
|
||||||
|
bool messageReplyable = true;
|
||||||
|
if (this->flags.hasAny({MessageFlag::System, MessageFlag::Subscription,
|
||||||
|
MessageFlag::Timeout, MessageFlag::Whisper,
|
||||||
|
MessageFlag::ModerationAction,
|
||||||
|
MessageFlag::InvalidReplyTarget}) ||
|
||||||
|
this->serverReceivedTime.secsTo(QDateTime::currentDateTime()) >
|
||||||
|
oneDayInSeconds)
|
||||||
|
{
|
||||||
|
messageReplyable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->replyThread != nullptr)
|
||||||
|
{
|
||||||
|
if (const auto &rootPtr = this->replyThread->root(); rootPtr != nullptr)
|
||||||
|
{
|
||||||
|
assert(this != rootPtr.get());
|
||||||
|
if (rootPtr->isReplyable() == ReplyStatus::NotReplyable)
|
||||||
|
{
|
||||||
|
// thread parent must be replyable to be replyable
|
||||||
|
return ReplyStatus::NotReplyableDueToThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageReplyable ? ReplyStatus::ReplyableWithThread
|
||||||
|
: ReplyStatus::NotReplyableWithThread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageReplyable ? ReplyStatus::Replyable
|
||||||
|
: ReplyStatus::NotReplyable;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -62,6 +62,29 @@ struct Message {
|
|||||||
// The root of the thread does not have replyThread set.
|
// The root of the thread does not have replyThread set.
|
||||||
std::shared_ptr<MessageThread> replyThread;
|
std::shared_ptr<MessageThread> replyThread;
|
||||||
MessagePtr replyParent;
|
MessagePtr replyParent;
|
||||||
|
enum class ReplyStatus : std::uint8_t {
|
||||||
|
/// message has no reply thread, and message is not replyable
|
||||||
|
///
|
||||||
|
/// e.g. due to message being deleted or too old
|
||||||
|
NotReplyable,
|
||||||
|
|
||||||
|
/// message has no reply thread, but message is replyable
|
||||||
|
Replyable,
|
||||||
|
|
||||||
|
/// message is part of a reply thread. both message & thread are replyable
|
||||||
|
ReplyableWithThread,
|
||||||
|
|
||||||
|
/// message is part of a reply thread. thread is replyable, but message is not replyable
|
||||||
|
///
|
||||||
|
/// e.g. due to message being deleted or too old
|
||||||
|
NotReplyableWithThread,
|
||||||
|
|
||||||
|
/// message is part of a reply thread. neither reply or message is replyable
|
||||||
|
///
|
||||||
|
/// e.g. due to message at the top of the thread being deleted
|
||||||
|
NotReplyableDueToThread,
|
||||||
|
};
|
||||||
|
ReplyStatus isReplyable() const;
|
||||||
uint32_t count = 1;
|
uint32_t count = 1;
|
||||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ enum class MessageFlag : std::int64_t {
|
|||||||
/// - forsen added "blockedterm" as a blocked term
|
/// - forsen added "blockedterm" as a blocked term
|
||||||
/// - Your message is being checked by mods and has not been sent
|
/// - Your message is being checked by mods and has not been sent
|
||||||
ModerationAction = (1LL << 41),
|
ModerationAction = (1LL << 41),
|
||||||
|
/// The message can't be replied to
|
||||||
|
/// Examples:
|
||||||
|
/// - message was deleted via single channel chat message deletion (IRC: CLEARMSG, EVENTSUB: channel.chat.message_delete)
|
||||||
|
/// - message was deleted via chat clear user messages (IRC: CLEARCHAT(user), EVENTSUB: channel.chat.clear_user_messages)
|
||||||
|
/// Note: If this message is inside a reply thread, the root must not have the flag either.
|
||||||
|
InvalidReplyTarget = (1LL << 42),
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
|||||||
@@ -544,22 +544,4 @@ void MessageLayout::addSelectionText(QString &str, uint32_t from, uint32_t to,
|
|||||||
this->container_.addSelectionText(str, from, to, copymode);
|
this->container_.addSelectionText(str, from, to, copymode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageLayout::isReplyable() const
|
|
||||||
{
|
|
||||||
if (this->message_->loginName.isEmpty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->message_->flags.hasAny(
|
|
||||||
{MessageFlag::System, MessageFlag::Subscription,
|
|
||||||
MessageFlag::Timeout, MessageFlag::Whisper,
|
|
||||||
MessageFlag::ModerationAction}))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ public:
|
|||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
bool isDisabled() const;
|
bool isDisabled() const;
|
||||||
bool isReplyable() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// methods
|
// methods
|
||||||
|
|||||||
@@ -329,6 +329,39 @@ void IrcMessageHandler::parseMessageInto(Communi::IrcMessage *message,
|
|||||||
sink.addOrReplaceTimeout(std::move(clearChat.message), time);
|
sink.addOrReplaceTimeout(std::move(clearChat.message), time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command == u"CLEARMSG"_s)
|
||||||
|
{
|
||||||
|
// check parameter count
|
||||||
|
if (message->parameters().length() < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString chanName;
|
||||||
|
if (!trimChannelName(message->parameter(0), chanName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tags = message->tags();
|
||||||
|
|
||||||
|
QString targetID = tags.value("target-msg-id").toString();
|
||||||
|
|
||||||
|
auto msg = sink.findMessageByID(targetID);
|
||||||
|
if (msg == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->flags.set(MessageFlag::Disabled);
|
||||||
|
msg->flags.set(MessageFlag::InvalidReplyTarget);
|
||||||
|
if (!getSettings()->hideDeletionActions)
|
||||||
|
{
|
||||||
|
sink.addMessage(MessageBuilder::makeDeletionMessageFromIRC(msg),
|
||||||
|
MessageContext::Original);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
@@ -519,6 +552,7 @@ void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message)
|
|||||||
}
|
}
|
||||||
|
|
||||||
msg->flags.set(MessageFlag::Disabled);
|
msg->flags.set(MessageFlag::Disabled);
|
||||||
|
msg->flags.set(MessageFlag::InvalidReplyTarget);
|
||||||
if (!getSettings()->hideDeletionActions)
|
if (!getSettings()->hideDeletionActions)
|
||||||
{
|
{
|
||||||
chan->addMessage(MessageBuilder::makeDeletionMessageFromIRC(msg),
|
chan->addMessage(MessageBuilder::makeDeletionMessageFromIRC(msg),
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ void addOrReplaceChannelTimeout(const Buf &buffer, MessagePtr message,
|
|||||||
// FOURTF: disabled for now
|
// FOURTF: disabled for now
|
||||||
// PAJLADA: Shitty solution described in Message.hpp
|
// PAJLADA: Shitty solution described in Message.hpp
|
||||||
s->flags.set(MessageFlag::Disabled);
|
s->flags.set(MessageFlag::Disabled);
|
||||||
|
s->flags.set(MessageFlag::InvalidReplyTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2615,19 +2615,72 @@ void ChannelView::addMessageContextMenuItems(QMenu *menu,
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Only display reply option where it makes sense
|
// Only display reply option where it makes sense
|
||||||
if (this->canReplyToMessages() && layout->isReplyable())
|
if (this->canReplyToMessages())
|
||||||
{
|
{
|
||||||
const auto &messagePtr = layout->getMessagePtr();
|
const auto &messagePtr = layout->getMessagePtr();
|
||||||
menu->addAction("&Reply to message", [this, &messagePtr] {
|
switch (messagePtr->isReplyable())
|
||||||
this->setInputReply(messagePtr);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (messagePtr->replyThread != nullptr)
|
|
||||||
{
|
{
|
||||||
menu->addAction("Reply to &original thread", [this, &messagePtr] {
|
case Message::ReplyStatus::Replyable: {
|
||||||
this->setInputReply(messagePtr->replyThread->root());
|
menu->addAction("&Reply to message", [this, &messagePtr] {
|
||||||
});
|
this->setInputReply(messagePtr);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Message::ReplyStatus::NotReplyable: {
|
||||||
|
const auto &replyAction =
|
||||||
|
menu->addAction("&Reply to message", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr);
|
||||||
|
});
|
||||||
|
replyAction->setEnabled(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Message::ReplyStatus::ReplyableWithThread: {
|
||||||
|
menu->addAction("&Reply to message", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr);
|
||||||
|
});
|
||||||
|
menu->addAction(
|
||||||
|
"Reply to &original thread", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr->replyThread->root());
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Message::ReplyStatus::NotReplyableWithThread: {
|
||||||
|
const auto &replyAction =
|
||||||
|
menu->addAction("&Reply to message", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr);
|
||||||
|
});
|
||||||
|
replyAction->setEnabled(false);
|
||||||
|
|
||||||
|
menu->addAction(
|
||||||
|
"Reply to &original thread", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr->replyThread->root());
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Message::ReplyStatus::NotReplyableDueToThread: {
|
||||||
|
const auto &replyAction =
|
||||||
|
menu->addAction("&Reply to message", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr);
|
||||||
|
});
|
||||||
|
replyAction->setEnabled(false);
|
||||||
|
|
||||||
|
const auto &replyThreadAction = menu->addAction(
|
||||||
|
"Reply to &original thread", [this, &messagePtr] {
|
||||||
|
this->setInputReply(messagePtr->replyThread->root());
|
||||||
|
});
|
||||||
|
|
||||||
|
replyThreadAction->setEnabled(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto &messagePtr = layout->getMessagePtr();
|
||||||
|
messagePtr->replyThread != nullptr)
|
||||||
|
{
|
||||||
menu->addAction("View &thread", [this, &messagePtr] {
|
menu->addAction("View &thread", [this, &messagePtr] {
|
||||||
this->showReplyThreadPopup(messagePtr);
|
this->showReplyThreadPopup(messagePtr);
|
||||||
});
|
});
|
||||||
@@ -3057,7 +3110,11 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Link::ReplyToMessage: {
|
case Link::ReplyToMessage: {
|
||||||
this->setInputReply(layout->getMessagePtr());
|
if (layout->getMessagePtr()->isReplyable() !=
|
||||||
|
Message::ReplyStatus::NotReplyable)
|
||||||
|
{
|
||||||
|
this->setInputReply(layout->getMessagePtr());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Link::ViewThread: {
|
case Link::ViewThread: {
|
||||||
|
|||||||
Reference in New Issue
Block a user