Context-aware phrases in topic service messages.

This commit is contained in:
John Preston 2022-10-25 16:42:03 +04:00
parent 97d8aa0a0d
commit aac91a19ca
9 changed files with 239 additions and 39 deletions

View file

@ -1508,12 +1508,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot."; "lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot.";
"lng_action_gift_received" = "{user} sent you a gift for {cost}"; "lng_action_gift_received" = "{user} sent you a gift for {cost}";
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}"; "lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
"lng_action_topic_created" = "Topic created"; "lng_action_topic_created_inside" = "Topic created";
"lng_action_topic_renamed" = "Topic renamed to «{title}»"; "lng_action_topic_closed_inside" = "Topics closed";
"lng_action_topic_icon_changed" = "Topic icon changed to {emoji}"; "lng_action_topic_reopened_inside" = "Topic reopened";
"lng_action_topic_icon_removed" = "Topic icon removed"; "lng_action_topic_created" = "{topic} — was created";
"lng_action_topic_closed" = "Topic closed"; "lng_action_topic_closed" = "{topic} — was closed";
"lng_action_topic_reopened" = "Topic reopened"; "lng_action_topic_reopened" = "{topic} — was reopened";
"lng_action_topic_placeholder" = "topic";
"lng_action_topic_renamed" = "{from} renamed the {link} to «{title}»";
"lng_action_topic_icon_changed" = "{from} changed the {link} icon to {emoji}";
"lng_action_topic_icon_removed" = "{from} removed the {link} icon";
"lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months"; "lng_premium_gift_duration_months#other" = "for {count} months";
@ -2019,6 +2023,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_replies_header#one" = "{count} reply"; "lng_replies_header#one" = "{count} reply";
"lng_replies_header#other" = "{count} replies"; "lng_replies_header#other" = "{count} replies";
"lng_replies_header_none" = "Replies"; "lng_replies_header_none" = "Replies";
"lng_replies_view_topic" = "View in Topic";
"lng_comments_header#one" = "{count} comment"; "lng_comments_header#one" = "{count} comment";
"lng_comments_header#other" = "{count} comments"; "lng_comments_header#other" = "{count} comments";
"lng_comments_header_none" = "Comments"; "lng_comments_header_none" = "Comments";
@ -3530,7 +3535,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forum_topic_closed" = "This topic is now closed."; "lng_forum_topic_closed" = "This topic is now closed.";
"lng_forum_topic_delete" = "Delete"; "lng_forum_topic_delete" = "Delete";
"lng_forum_topic_delete_sure" = "Are you sure you want to delete this topic?"; "lng_forum_topic_delete_sure" = "Are you sure you want to delete this topic?";
"lng_forum_topic_created_title_my" = "Almost done!";
"lng_forum_topic_created_body_my" = "Send the first message to start this topic.";
"lng_forum_topic_created_title" = "Topic started!";
"lng_forum_topic_created_body" = "Send a message to open the discussion";
"lng_forum_topics_switch" = "Topics"; "lng_forum_topics_switch" = "Topics";
"lng_forum_topics_not_enough#one" = "Only groups with more than **{count} member** can have topics enabled.";
"lng_forum_topics_not_enough#other" = "Only groups with more than **{count} members** can have topics enabled.";
"lng_forum_topics_no_discussion" = "Topics can't be enabled in discussion groups at the moment.";
"lng_forum_no_topics" = "No topics currently created in this forum."; "lng_forum_no_topics" = "No topics currently created in this forum.";
"lng_forum_create_topic" = "Create topic"; "lng_forum_create_topic" = "Create topic";
"lng_forum_discard_sure" = "Are you sure you want to discard this topic?"; "lng_forum_discard_sure" = "Are you sure you want to discard this topic?";

View file

@ -639,16 +639,11 @@ TextWithEntities GenerateDefaultBannedRightsChangeText(
? wrapIcon(data.vicon_emoji_id()->v) ? wrapIcon(data.vicon_emoji_id()->v)
: TextWithEntities(); : TextWithEntities();
result.append(qs(data.vtitle())); result.append(qs(data.vtitle()));
result.entities.insert( return Ui::Text::Link(
result.entities.begin(), std::move(result),
EntityInText( u"internal:url:https://t.me/c/%1?topic=%2"_q.arg(
EntityType::CustomUrl, peerToChannel(channel->id).bare).arg(
0, data.vid().v));
result.text.size(),
u"https://t.me/c/%1?topic=%2"_q.arg(
peerToChannel(channel->id).bare).arg(
data.vid().v)));
return result;
}, [](const MTPDforumTopicDeleted &) { }, [](const MTPDforumTopicDeleted &) {
return TextWithEntities{ u"Deleted"_q }; return TextWithEntities{ u"Deleted"_q };
}); });

View file

@ -2109,7 +2109,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
? item->id ? item->id
: item->replyToTop(); : item->replyToTop();
const auto phrase = topicRootId const auto phrase = topicRootId
? u"View in Thread"_q // #TODO lang-forum ? tr::lng_replies_view_topic(tr::now)
: (repliesCount > 0) : (repliesCount > 0)
? tr::lng_replies_view( ? tr::lng_replies_view(
tr::now, tr::now,

View file

@ -638,7 +638,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) {
auto result = PreparedText{}; auto result = PreparedText{};
result.text = { tr::lng_action_topic_created(tr::now) }; result.text = { tr::lng_action_topic_created_inside(tr::now) };
return result; return result;
}; };
@ -657,23 +657,34 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
}; };
if (const auto closed = action.vclosed()) { if (const auto closed = action.vclosed()) {
result.text = { mtpIsTrue(*closed) result.text = { mtpIsTrue(*closed)
? tr::lng_action_topic_closed(tr::now) ? tr::lng_action_topic_closed_inside(tr::now)
: tr::lng_action_topic_reopened(tr::now) }; : tr::lng_action_topic_reopened_inside(tr::now) };
} else if (!action.vtitle()) { } else if (!action.vtitle()) {
if (const auto icon = action.vicon_emoji_id()) { if (const auto icon = action.vicon_emoji_id()) {
if (const auto iconId = icon->v) { if (const auto iconId = icon->v) {
result.links.push_back(fromLink());
result.text = tr::lng_action_topic_icon_changed( result.text = tr::lng_action_topic_icon_changed(
tr::now, tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_link,
{ tr::lng_action_topic_placeholder(tr::now) },
lt_emoji, lt_emoji,
wrapIcon(iconId), wrapIcon(iconId),
Ui::Text::WithEntities); Ui::Text::WithEntities);
} else { } else {
result.text = { result.links.push_back(fromLink());
tr::lng_action_topic_icon_removed(tr::now) result.text = tr::lng_action_topic_icon_removed(
}; tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_link,
{ tr::lng_action_topic_placeholder(tr::now) },
Ui::Text::WithEntities);
} }
} }
} else { } else {
result.links.push_back(fromLink());
auto title = TextWithEntities{ auto title = TextWithEntities{
qs(*action.vtitle()) qs(*action.vtitle())
}; };
@ -682,6 +693,10 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
} }
result.text = tr::lng_action_topic_renamed( result.text = tr::lng_action_topic_renamed(
tr::now, tr::now,
lt_from,
fromLinkText(), // Link 1.
lt_link,
{ tr::lng_action_topic_placeholder(tr::now) },
lt_title, lt_title,
std::move(title), std::move(title),
Ui::Text::WithEntities); Ui::Text::WithEntities);
@ -996,7 +1011,7 @@ HistoryService::PreparedText HistoryService::preparePinnedText() {
original = Ui::Text::Mid(original, 0, cutAt).append( original = Ui::Text::Mid(original, 0, cutAt).append(
Ui::kQEllipsis); Ui::kQEllipsis);
} }
original = Ui::Text::Wrapped( original = Ui::Text::Link(
Ui::Text::Filtered( Ui::Text::Filtered(
std::move(original), std::move(original),
{ {
@ -1005,8 +1020,7 @@ HistoryService::PreparedText HistoryService::preparePinnedText() {
EntityType::Italic, EntityType::Italic,
EntityType::CustomEmoji, EntityType::CustomEmoji,
}), }),
EntityType::CustomUrl, 2);
Ui::Text::Link({}, 2).entities.front().data());
result.text = tr::lng_action_pinned_message( result.text = tr::lng_action_pinned_message(
tr::now, tr::now,
lt_from, lt_from,
@ -1478,23 +1492,44 @@ void HistoryService::createFromMtp(const MTPDmessage &message) {
} }
void HistoryService::createFromMtp(const MTPDmessageService &message) { void HistoryService::createFromMtp(const MTPDmessageService &message) {
const auto type = message.vaction().type(); const auto &action = message.vaction();
const auto type = action.type();
if (type == mtpc_messageActionPinMessage) { if (type == mtpc_messageActionPinMessage) {
UpdateComponents(HistoryServicePinned::Bit()); UpdateComponents(HistoryServicePinned::Bit());
} else if (type == mtpc_messageActionTopicCreate } else if (type == mtpc_messageActionTopicCreate
|| type == mtpc_messageActionTopicEdit) { || type == mtpc_messageActionTopicEdit) {
UpdateComponents(HistoryServiceTopicInfo::Bit()); UpdateComponents(HistoryServiceTopicInfo::Bit());
Get<HistoryServiceTopicInfo>()->topicPost = true; const auto info = Get<HistoryServiceTopicInfo>();
info->topicPost = true;
if (type == mtpc_messageActionTopicEdit) {
const auto &data = action.c_messageActionTopicEdit();
if (const auto title = data.vtitle()) {
info->title = qs(*title);
info->renamed = true;
}
if (const auto icon = data.vicon_emoji_id()) {
info->iconId = icon->v;
info->reiconed = true;
}
if (const auto closed = data.vclosed()) {
info->closed = mtpIsTrue(*closed);
info->reopened = !info->closed;
}
} else {
const auto &data = action.c_messageActionTopicCreate();
info->title = qs(data.vtitle());
info->iconId = data.vicon_emoji_id().value_or_empty();
}
} else if (type == mtpc_messageActionSetChatTheme) { } else if (type == mtpc_messageActionSetChatTheme) {
setupChatThemeChange(); setupChatThemeChange();
} else if (type == mtpc_messageActionSetMessagesTTL) { } else if (type == mtpc_messageActionSetMessagesTTL) {
setupTTLChange(); setupTTLChange();
} else if (type == mtpc_messageActionGameScore) { } else if (type == mtpc_messageActionGameScore) {
const auto &data = message.vaction().c_messageActionGameScore(); const auto &data = action.c_messageActionGameScore();
UpdateComponents(HistoryServiceGameScore::Bit()); UpdateComponents(HistoryServiceGameScore::Bit());
Get<HistoryServiceGameScore>()->score = data.vscore().v; Get<HistoryServiceGameScore>()->score = data.vscore().v;
} else if (type == mtpc_messageActionPaymentSent) { } else if (type == mtpc_messageActionPaymentSent) {
const auto &data = message.vaction().c_messageActionPaymentSent(); const auto &data = action.c_messageActionPaymentSent();
UpdateComponents(HistoryServicePayment::Bit()); UpdateComponents(HistoryServicePayment::Bit());
const auto amount = data.vtotal_amount().v; const auto amount = data.vtotal_amount().v;
const auto currency = qs(data.vcurrency()); const auto currency = qs(data.vcurrency());
@ -1521,10 +1556,10 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
|| type == mtpc_messageActionGroupCallScheduled) { || type == mtpc_messageActionGroupCallScheduled) {
const auto started = (type == mtpc_messageActionGroupCall); const auto started = (type == mtpc_messageActionGroupCall);
const auto &callData = started const auto &callData = started
? message.vaction().c_messageActionGroupCall().vcall() ? action.c_messageActionGroupCall().vcall()
: message.vaction().c_messageActionGroupCallScheduled().vcall(); : action.c_messageActionGroupCallScheduled().vcall();
const auto duration = started const auto duration = started
? message.vaction().c_messageActionGroupCall().vduration() ? action.c_messageActionGroupCall().vduration()
: tl::conditional<MTPint>(); : tl::conditional<MTPint>();
if (duration) { if (duration) {
RemoveComponents(HistoryServiceOngoingCall::Bit()); RemoveComponents(HistoryServiceOngoingCall::Bit());
@ -1535,7 +1570,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
call->link = GroupCallClickHandler(history()->peer, call->id); call->link = GroupCallClickHandler(history()->peer, call->id);
} }
} else if (type == mtpc_messageActionInviteToGroupCall) { } else if (type == mtpc_messageActionInviteToGroupCall) {
const auto &data = message.vaction().c_messageActionInviteToGroupCall(); const auto &data = action.c_messageActionInviteToGroupCall();
const auto id = CallIdFromInput(data.vcall()); const auto id = CallIdFromInput(data.vcall());
const auto peer = history()->peer; const auto peer = history()->peer;
const auto has = PeerHasThisCall(peer, id); const auto has = PeerHasThisCall(peer, id);
@ -1586,7 +1621,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
} }
}); });
} }
setMessageByAction(message.vaction()); setMessageByAction(action);
} }
const std::vector<ClickHandlerPtr> &HistoryService::customTextLinks() const { const std::vector<ClickHandlerPtr> &HistoryService::customTextLinks() const {

View file

@ -30,6 +30,12 @@ struct HistoryServicePinned
struct HistoryServiceTopicInfo struct HistoryServiceTopicInfo
: public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem> : public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem>
, public HistoryServiceDependentData { , public HistoryServiceDependentData {
QString title;
DocumentId iconId = 0;
bool closed = false;
bool reopened = false;
bool reiconed = false;
bool renamed = false;
}; };
struct HistoryServiceGameScore struct HistoryServiceGameScore

View file

@ -633,7 +633,7 @@ bool AddViewRepliesAction(
: item->replyToTop(); : item->replyToTop();
const auto highlightId = topicRootId ? item->id : 0; const auto highlightId = topicRootId ? item->id : 0;
const auto phrase = topicRootId const auto phrase = topicRootId
? u"View in Thread"_q // #TODO lang-forum ? tr::lng_replies_view_topic(tr::now)
: (repliesCount > 0) : (repliesCount > 0)
? tr::lng_replies_view( ? tr::lng_replies_view(
tr::now, tr::now,

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_spoiler_click_handler.h" #include "history/view/history_view_spoiler_click_handler.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_service.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
@ -37,10 +38,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h" #include "ui/toasts/common_toasts.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/item_text_options.h" #include "ui/item_text_options.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_groups.h" #include "data/data_groups.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_sponsored_messages.h" #include "data/data_sponsored_messages.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
@ -657,6 +661,141 @@ int Element::textHeightFor(int textWidth) {
return _textHeight; return _textHeight;
} }
auto Element::contextDependentServiceText() -> TextWithLinks {
if (_delegate->elementContext() == Context::Replies) {
return {};
}
const auto item = data();
const auto info = item->Get<HistoryServiceTopicInfo>();
if (!info) {
return {};
}
const auto peerId = item->history()->peer->id;
const auto topicRootId = item->topicRootId();
if (!topicRootId || !peerIsChannel(peerId)) {
return {};
}
const auto from = item->from();
const auto wrapIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({ .id = id }))
},
};
};
const auto topicUrl = u"internal:url:https://t.me/c/%1?topic=%2"_q
.arg(peerToChannel(peerId).bare)
.arg(topicRootId.bare);
const auto fromLink = [&](int index) {
return Ui::Text::Link(from->name(), index);
};
const auto placeholderLink = [&] {
return Ui::Text::Link(
tr::lng_action_topic_placeholder(tr::now),
topicUrl);
};
const auto wrapTopic = [&](
const QString &title,
std::optional<DocumentId> iconId) {
auto result = TextWithEntities{ title };
auto full = iconId
? wrapIcon(*iconId).append(' ').append(std::move(result))
: result;
return Ui::Text::Link(std::move(full), topicUrl);
};
const auto wrapParentTopic = [&] {
const auto forum = history()->peer->forum();
if (!forum || forum->topicDeleted(topicRootId)) {
return wrapTopic(
tr::lng_deleted_message(tr::now),
std::nullopt);
} else if (const auto topic = forum->topicFor(topicRootId)) {
return wrapTopic(topic->title(), topic->iconId());
} else {
forum->requestTopic(topicRootId, crl::guard(this, [=] {
itemTextUpdated();
history()->owner().requestViewResize(this);
}));
return wrapTopic(
tr::lng_profile_loading(tr::now),
std::nullopt);
}
};
if (info->closed) {
return {
tr::lng_action_topic_closed(
tr::now,
lt_topic,
wrapParentTopic(),
Ui::Text::WithEntities),
};
} else if (info->reopened) {
return {
tr::lng_action_topic_reopened(
tr::now,
lt_topic,
wrapParentTopic(),
Ui::Text::WithEntities),
};
} else if (info->renamed) {
return {
tr::lng_action_topic_renamed(
tr::now,
lt_from,
fromLink(1),
lt_link,
placeholderLink(),
lt_title,
wrapTopic(
info->title,
(info->reiconed
? info->iconId
: std::optional<DocumentId>())),
Ui::Text::WithEntities),
{ from->createOpenLink() },
};
} else if (info->reiconed) {
if (const auto iconId = info->iconId) {
return {
tr::lng_action_topic_icon_changed(
tr::now,
lt_from,
fromLink(1),
lt_link,
placeholderLink(),
lt_emoji,
wrapIcon(iconId),
Ui::Text::WithEntities),
{ from->createOpenLink() },
};
} else {
return {
tr::lng_action_topic_icon_removed(
tr::now,
lt_from,
fromLink(1),
lt_link,
placeholderLink(),
Ui::Text::WithEntities),
{ from->createOpenLink() },
};
}
} else {
return {
tr::lng_action_topic_created(
tr::now,
lt_topic,
wrapTopic(info->title, info->iconId),
Ui::Text::WithEntities),
};
}
}
void Element::validateText() { void Element::validateText() {
const auto item = data(); const auto item = data();
const auto &text = item->_text; const auto &text = item->_text;
@ -668,13 +807,20 @@ void Element::validateText() {
.customEmojiRepaint = [=] { customEmojiRepaint(); }, .customEmojiRepaint = [=] { customEmojiRepaint(); },
}; };
if (_flags & Flag::ServiceMessage) { if (_flags & Flag::ServiceMessage) {
const auto contextDependentText = contextDependentServiceText();
const auto &markedText = contextDependentText.text.empty()
? text
: contextDependentText.text;
const auto &customLinks = contextDependentText.text.empty()
? item->customTextLinks()
: contextDependentText.links;
_text.setMarkedText( _text.setMarkedText(
st::serviceTextStyle, st::serviceTextStyle,
text, markedText,
Ui::ItemTextServiceOptions(), Ui::ItemTextServiceOptions(),
context); context);
auto linkIndex = 0; auto linkIndex = 0;
for (const auto &link : item->customTextLinks()) { for (const auto &link : customLinks) {
// Link indices start with 1. // Link indices start with 1.
_text.setLink(++linkIndex, link); _text.setLink(++linkIndex, link);
} }

View file

@ -515,6 +515,12 @@ private:
void refreshMedia(Element *replacing); void refreshMedia(Element *replacing);
struct TextWithLinks {
TextWithEntities text;
std::vector<ClickHandlerPtr> links;
};
[[nodiscard]] TextWithLinks contextDependentServiceText();
const not_null<ElementDelegate*> _delegate; const not_null<ElementDelegate*> _delegate;
const not_null<HistoryItem*> _data; const not_null<HistoryItem*> _data;
HistoryBlock *_block = nullptr; HistoryBlock *_block = nullptr;

@ -1 +1 @@
Subproject commit c199a1722fae72e254753f3095444a3c82a2a704 Subproject commit 7f1dd3c351f534d0564c41376176eb0cd1be4184