mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Open usercard on mention click (#1674)
This commit is contained in:
parent
276f3e1d98
commit
ba06b10135
|
@ -4,6 +4,7 @@
|
|||
|
||||
- Major: We now support image thumbnails coming from the link resolver. This feature is off by default and can be enabled in the settings with the "Show link thumbnail" setting. This feature also requires the "Show link info when hovering" setting to be enabled (#1664)
|
||||
- Major: Added image upload functionality to i.nuuls.com with an ability to change upload destination. This works by dragging and dropping an image into a split, or pasting an image into the text edit field. (#1332, #1741)
|
||||
- Minor: Clicking on @mentions will open the User Popup. (#1674)
|
||||
- Minor: You can now open the Twitch User Card by middle-mouse clicking a username. (#1669)
|
||||
- Minor: User Popup now also includes recent user messages (#1729)
|
||||
- Minor: BetterTTV / FrankerFaceZ emote tooltips now also have emote authors' name (#1721)
|
||||
|
|
|
@ -148,9 +148,8 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
|
|||
|
||||
int count = s->count + 1;
|
||||
|
||||
MessageBuilder replacement(systemMessage,
|
||||
message->searchText + QString(" (") +
|
||||
QString::number(count) + " times)");
|
||||
MessageBuilder replacement(timeoutMessage, message->searchText,
|
||||
count);
|
||||
|
||||
replacement->timeoutUser = message->timeoutUser;
|
||||
replacement->count = count;
|
||||
|
|
|
@ -63,6 +63,11 @@ void UsernameSet::insertPrefix(const QString &value)
|
|||
string = value;
|
||||
}
|
||||
|
||||
bool UsernameSet::contains(const QString &value) const
|
||||
{
|
||||
return this->items.count(value) == 1;
|
||||
}
|
||||
|
||||
//
|
||||
// Range
|
||||
//
|
||||
|
|
|
@ -76,6 +76,8 @@ public:
|
|||
std::pair<Iterator, bool> insert(const QString &value);
|
||||
std::pair<Iterator, bool> insert(QString &&value);
|
||||
|
||||
bool contains(const QString &value) const;
|
||||
|
||||
private:
|
||||
void insertPrefix(const QString &string);
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@ MessagePtr makeSystemMessage(const QString &text)
|
|||
return MessageBuilder(systemMessage, text).release();
|
||||
}
|
||||
|
||||
MessagePtr makeSystemMessage(const QString &text, const QTime &time)
|
||||
{
|
||||
return MessageBuilder(systemMessage, text, time).release();
|
||||
}
|
||||
|
||||
std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||
const AutomodAction &action)
|
||||
{
|
||||
|
@ -93,10 +98,11 @@ MessageBuilder::MessageBuilder()
|
|||
{
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text)
|
||||
MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text,
|
||||
const QTime &time)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->emplace<TimestampElement>(time);
|
||||
|
||||
// check system message for links
|
||||
// (e.g. needed for sub ticket message in sub only mode)
|
||||
|
@ -120,17 +126,40 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text)
|
|||
this->message().searchText = text;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(TimeoutMessageTag,
|
||||
const QString &systemMessageText, int times)
|
||||
: MessageBuilder()
|
||||
{
|
||||
QString username = systemMessageText.split(" ").at(0);
|
||||
QString remainder = systemMessageText.mid(username.length() + 1);
|
||||
|
||||
QString text;
|
||||
|
||||
this->emplace<TimestampElement>();
|
||||
this->emplaceSystemTextAndUpdate(username, text)
|
||||
->setLink({Link::UserInfo, username});
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("%1 (%2 times)").arg(remainder.trimmed()).arg(times), text);
|
||||
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||
const QString &durationInSeconds,
|
||||
const QString &reason, bool multipleTimes)
|
||||
: MessageBuilder()
|
||||
{
|
||||
QString fullText;
|
||||
QString text;
|
||||
|
||||
text.append(username);
|
||||
this->emplace<TimestampElement>();
|
||||
this->emplaceSystemTextAndUpdate(username, fullText)
|
||||
->setLink({Link::UserInfo, username});
|
||||
|
||||
if (!durationInSeconds.isEmpty())
|
||||
{
|
||||
text.append(" has been timed out");
|
||||
text.append("has been timed out");
|
||||
|
||||
// TODO: Implement who timed the user out
|
||||
|
||||
|
@ -144,7 +173,7 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
|||
}
|
||||
else
|
||||
{
|
||||
text.append(" has been permanently banned");
|
||||
text.append("has been permanently banned");
|
||||
}
|
||||
|
||||
if (reason.length() > 0)
|
||||
|
@ -164,11 +193,10 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
|||
this->message().flags.set(MessageFlag::Timeout);
|
||||
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
|
||||
this->message().timeoutUser = username;
|
||||
this->emplace<TimestampElement>();
|
||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
|
||||
this->emplaceSystemTextAndUpdate(text, fullText);
|
||||
this->message().messageText = fullText;
|
||||
this->message().searchText = fullText;
|
||||
}
|
||||
|
||||
// XXX: This does not belong in the MessageBuilder, this should be part of the TwitchMessageBuilder
|
||||
|
@ -187,77 +215,82 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
|
|||
|
||||
if (action.target.id == current->getUserId())
|
||||
{
|
||||
text.append("You were ");
|
||||
this->emplaceSystemTextAndUpdate("You were", text);
|
||||
if (action.isBan())
|
||||
{
|
||||
text.append("banned");
|
||||
this->emplaceSystemTextAndUpdate("banned", text);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.append(
|
||||
QString("timed out for %1").arg(formatTime(action.duration)));
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("timed out for %1").arg(formatTime(action.duration)),
|
||||
text);
|
||||
}
|
||||
|
||||
if (!action.source.name.isEmpty())
|
||||
{
|
||||
text.append(" by ");
|
||||
text.append(action.source.name);
|
||||
this->emplaceSystemTextAndUpdate("by", text);
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
action.source.name + (action.reason.isEmpty() ? "." : ":"),
|
||||
text)
|
||||
->setLink({Link::UserInfo, action.source.name});
|
||||
}
|
||||
|
||||
if (action.reason.isEmpty())
|
||||
if (!action.reason.isEmpty())
|
||||
{
|
||||
text.append(".");
|
||||
}
|
||||
else
|
||||
{
|
||||
text.append(QString(": \"%1\".").arg(action.reason));
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("\"%1\".").arg(action.reason), text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (action.isBan())
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.source.name, text)
|
||||
->setLink({Link::UserInfo, action.source.name});
|
||||
this->emplaceSystemTextAndUpdate("banned", text);
|
||||
if (action.reason.isEmpty())
|
||||
{
|
||||
text = QString("%1 banned %2.") //
|
||||
.arg(action.source.name)
|
||||
.arg(action.target.name);
|
||||
this->emplaceSystemTextAndUpdate(action.target.name, text)
|
||||
->setLink({Link::UserInfo, action.target.name});
|
||||
}
|
||||
else
|
||||
{
|
||||
text = QString("%1 banned %2: \"%3\".") //
|
||||
.arg(action.source.name)
|
||||
.arg(action.target.name)
|
||||
.arg(action.reason);
|
||||
this->emplaceSystemTextAndUpdate(action.target.name + ":", text)
|
||||
->setLink({Link::UserInfo, action.target.name});
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("\"%1\".").arg(action.reason), text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplaceSystemTextAndUpdate(action.source.name, text)
|
||||
->setLink({Link::UserInfo, action.source.name});
|
||||
this->emplaceSystemTextAndUpdate("timed out", text);
|
||||
this->emplaceSystemTextAndUpdate(action.target.name, text)
|
||||
->setLink({Link::UserInfo, action.target.name});
|
||||
if (action.reason.isEmpty())
|
||||
{
|
||||
text = QString("%1 timed out %2 for %3.") //
|
||||
.arg(action.source.name)
|
||||
.arg(action.target.name)
|
||||
.arg(formatTime(action.duration));
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("for %1.").arg(formatTime(action.duration)), text);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = QString("%1 timed out %2 for %3: \"%4\".") //
|
||||
.arg(action.source.name)
|
||||
.arg(action.target.name)
|
||||
.arg(formatTime(action.duration))
|
||||
.arg(action.reason);
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("for %1: \"%2\".")
|
||||
.arg(formatTime(action.duration))
|
||||
.arg(action.reason),
|
||||
text);
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
text.append(QString(" (%1 times)").arg(count));
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
QString("(%1 times)").arg(count), text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
@ -271,14 +304,15 @@ MessageBuilder::MessageBuilder(const UnbanAction &action)
|
|||
|
||||
this->message().timeoutUser = action.target.name;
|
||||
|
||||
QString text =
|
||||
QString("%1 %2 %3.")
|
||||
.arg(action.source.name)
|
||||
.arg(QString(action.wasBan() ? "unbanned" : "untimedout"))
|
||||
.arg(action.target.name);
|
||||
QString text;
|
||||
|
||||
this->emplaceSystemTextAndUpdate(action.source.name, text)
|
||||
->setLink({Link::UserInfo, action.source.name});
|
||||
this->emplaceSystemTextAndUpdate(
|
||||
action.wasBan() ? "unbanned" : "untimedout", text);
|
||||
this->emplaceSystemTextAndUpdate(action.target.name, text)
|
||||
->setLink({Link::UserInfo, action.target.name});
|
||||
|
||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
this->message().messageText = text;
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
@ -446,4 +480,12 @@ void MessageBuilder::addLink(const QString &origLink,
|
|||
});
|
||||
}
|
||||
|
||||
TextElement *MessageBuilder::emplaceSystemTextAndUpdate(const QString &text,
|
||||
QString &toUpdate)
|
||||
{
|
||||
toUpdate.append(text + " ");
|
||||
return this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -22,6 +22,7 @@ const SystemMessageTag systemMessage{};
|
|||
const TimeoutMessageTag timeoutMessage{};
|
||||
|
||||
MessagePtr makeSystemMessage(const QString &text);
|
||||
MessagePtr makeSystemMessage(const QString &text, const QTime &time);
|
||||
std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||
const AutomodAction &action);
|
||||
|
||||
|
@ -37,7 +38,10 @@ class MessageBuilder
|
|||
{
|
||||
public:
|
||||
MessageBuilder();
|
||||
MessageBuilder(SystemMessageTag, const QString &text);
|
||||
MessageBuilder(SystemMessageTag, const QString &text,
|
||||
const QTime &time = QTime::currentTime());
|
||||
MessageBuilder(TimeoutMessageTag, const QString &systemMessageText,
|
||||
int times);
|
||||
MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||
const QString &durationInSeconds, const QString &reason,
|
||||
bool multipleTimes);
|
||||
|
@ -67,6 +71,12 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
// Helper method that emplaces some text stylized as system text
|
||||
// and then appends that text to the QString parameter "toUpdate".
|
||||
// Returns the TextElement that was emplaced.
|
||||
TextElement *emplaceSystemTextAndUpdate(const QString &text,
|
||||
QString &toUpdate);
|
||||
|
||||
std::shared_ptr<Message> message_;
|
||||
};
|
||||
|
||||
|
|
|
@ -634,7 +634,25 @@ std::vector<MessagePtr> IrcMessageHandler::parseNoticeMessage(
|
|||
{
|
||||
std::vector<MessagePtr> builtMessages;
|
||||
|
||||
builtMessages.emplace_back(makeSystemMessage(message->content()));
|
||||
if (message->tags().contains("historical"))
|
||||
{
|
||||
bool customReceived = false;
|
||||
qint64 ts = message->tags()
|
||||
.value("rm-received-ts")
|
||||
.toLongLong(&customReceived);
|
||||
if (!customReceived)
|
||||
{
|
||||
ts = message->tags().value("tmi-sent-ts").toLongLong();
|
||||
}
|
||||
|
||||
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts);
|
||||
builtMessages.emplace_back(
|
||||
makeSystemMessage(message->content(), dateTime.time()));
|
||||
}
|
||||
else
|
||||
{
|
||||
builtMessages.emplace_back(makeSystemMessage(message->content()));
|
||||
}
|
||||
|
||||
return builtMessages;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
|
||||
namespace {
|
||||
|
||||
// matches a mention with punctuation at the end, like "@username," or "@username!!!" where capture group would return "username"
|
||||
const QRegularExpression mentionRegex("^@(\\w+)[.,!?;]*?$");
|
||||
|
||||
const QSet<QString> zeroWidthEmotes{
|
||||
"SoSnowy", "IceCold", "SantaHat", "TopHat",
|
||||
"ReinDeer", "CandyCane", "cvMask", "cvHazmat",
|
||||
|
@ -407,29 +410,50 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
|||
|
||||
// Actually just text
|
||||
auto linkString = this->matchLink(string);
|
||||
auto link = Link();
|
||||
auto textColor = this->action_ ? MessageColor(this->usernameColor_)
|
||||
: MessageColor(MessageColor::Text);
|
||||
|
||||
if (linkString.isEmpty())
|
||||
{
|
||||
if (string.startsWith('@'))
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
|
||||
textColor, FontStyle::ChatMediumBold);
|
||||
this->emplace<TextElement>(
|
||||
string, MessageElementFlag::NonBoldUsername, textColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::Text,
|
||||
textColor);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!linkString.isEmpty())
|
||||
{
|
||||
this->addLink(string, linkString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.startsWith('@'))
|
||||
{
|
||||
auto match = mentionRegex.match(string);
|
||||
// Only treat as @mention if valid username
|
||||
if (match.hasMatch())
|
||||
{
|
||||
QString username = match.captured(1);
|
||||
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
|
||||
textColor, FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, username});
|
||||
|
||||
this->emplace<TextElement>(
|
||||
string, MessageElementFlag::NonBoldUsername, textColor)
|
||||
->setLink({Link::UserInfo, username});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->twitchChannel != nullptr && getSettings()->findAllUsernames)
|
||||
{
|
||||
auto chatters = this->twitchChannel->accessChatters();
|
||||
if (chatters->contains(string))
|
||||
{
|
||||
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
|
||||
textColor, FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, string});
|
||||
|
||||
this->emplace<TextElement>(
|
||||
string, MessageElementFlag::NonBoldUsername, textColor)
|
||||
->setLink({Link::UserInfo, string});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->emplace<TextElement>(string, MessageElementFlag::Text, textColor);
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::parseMessageID()
|
||||
|
|
|
@ -94,6 +94,8 @@ public:
|
|||
BoolSetting enableSmoothScrollingNewMessages = {
|
||||
"/appearance/smoothScrollingNewMessages", false};
|
||||
BoolSetting boldUsernames = {"/appearance/messages/boldUsernames", false};
|
||||
BoolSetting findAllUsernames = {"/appearance/messages/findAllUsernames",
|
||||
false};
|
||||
// BoolSetting customizable splitheader
|
||||
BoolSetting headerViewerCount = {"/appearance/splitheader/showViewerCount",
|
||||
false};
|
||||
|
|
|
@ -511,6 +511,8 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
layout.addCheckbox("Show parted users (< 1000 chatters)", s.showParts);
|
||||
layout.addCheckbox("Lowercase domains (anti-phishing)", s.lowercaseDomains);
|
||||
layout.addCheckbox("Bold @usernames", s.boldUsernames);
|
||||
layout.addCheckbox("Try to find usernames without @ prefix",
|
||||
s.findAllUsernames);
|
||||
layout.addDropdown<float>(
|
||||
"Username font weight", {"50", "Default", "75", "100"}, s.boldScale,
|
||||
[](auto val) {
|
||||
|
|
Loading…
Reference in a new issue