From aac91a19cac6b3741b03a27609b31941a7751964 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 25 Oct 2022 16:42:03 +0400 Subject: [PATCH] Context-aware phrases in topic service messages. --- Telegram/Resources/langs/lang.strings | 24 ++- .../admin_log/history_admin_log_item.cpp | 15 +- .../history/history_inner_widget.cpp | 2 +- .../SourceFiles/history/history_service.cpp | 71 ++++++--- .../SourceFiles/history/history_service.h | 6 + .../view/history_view_context_menu.cpp | 2 +- .../history/view/history_view_element.cpp | 150 +++++++++++++++++- .../history/view/history_view_element.h | 6 + Telegram/lib_ui | 2 +- 9 files changed, 239 insertions(+), 39 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e0173e366..9a13a54cc 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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_gift_received" = "{user} sent you 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_renamed" = "Topic renamed to «{title}»"; -"lng_action_topic_icon_changed" = "Topic icon changed to {emoji}"; -"lng_action_topic_icon_removed" = "Topic icon removed"; -"lng_action_topic_closed" = "Topic closed"; -"lng_action_topic_reopened" = "Topic reopened"; +"lng_action_topic_created_inside" = "Topic created"; +"lng_action_topic_closed_inside" = "Topics closed"; +"lng_action_topic_reopened_inside" = "Topic reopened"; +"lng_action_topic_created" = "{topic} — was created"; +"lng_action_topic_closed" = "{topic} — was closed"; +"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#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#other" = "{count} replies"; "lng_replies_header_none" = "Replies"; +"lng_replies_view_topic" = "View in Topic"; "lng_comments_header#one" = "{count} comment"; "lng_comments_header#other" = "{count} 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_delete" = "Delete"; "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_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_create_topic" = "Create topic"; "lng_forum_discard_sure" = "Are you sure you want to discard this topic?"; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 699c8f3fe..bbc7c1438 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -639,16 +639,11 @@ TextWithEntities GenerateDefaultBannedRightsChangeText( ? wrapIcon(data.vicon_emoji_id()->v) : TextWithEntities(); result.append(qs(data.vtitle())); - result.entities.insert( - result.entities.begin(), - EntityInText( - EntityType::CustomUrl, - 0, - result.text.size(), - u"https://t.me/c/%1?topic=%2"_q.arg( - peerToChannel(channel->id).bare).arg( - data.vid().v))); - return result; + return Ui::Text::Link( + std::move(result), + u"internal:url:https://t.me/c/%1?topic=%2"_q.arg( + peerToChannel(channel->id).bare).arg( + data.vid().v)); }, [](const MTPDforumTopicDeleted &) { return TextWithEntities{ u"Deleted"_q }; }); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 08ba47a0e..23dff0464 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2109,7 +2109,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { ? item->id : item->replyToTop(); const auto phrase = topicRootId - ? u"View in Thread"_q // #TODO lang-forum + ? tr::lng_replies_view_topic(tr::now) : (repliesCount > 0) ? tr::lng_replies_view( tr::now, diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index c52281435..3df7d0567 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -638,7 +638,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { auto result = PreparedText{}; - result.text = { tr::lng_action_topic_created(tr::now) }; + result.text = { tr::lng_action_topic_created_inside(tr::now) }; return result; }; @@ -657,23 +657,34 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { }; if (const auto closed = action.vclosed()) { result.text = { mtpIsTrue(*closed) - ? tr::lng_action_topic_closed(tr::now) - : tr::lng_action_topic_reopened(tr::now) }; + ? tr::lng_action_topic_closed_inside(tr::now) + : tr::lng_action_topic_reopened_inside(tr::now) }; } else if (!action.vtitle()) { if (const auto icon = action.vicon_emoji_id()) { if (const auto iconId = icon->v) { + result.links.push_back(fromLink()); result.text = tr::lng_action_topic_icon_changed( tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, lt_emoji, wrapIcon(iconId), Ui::Text::WithEntities); } else { - result.text = { - tr::lng_action_topic_icon_removed(tr::now) - }; + result.links.push_back(fromLink()); + 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 { + result.links.push_back(fromLink()); auto title = TextWithEntities{ qs(*action.vtitle()) }; @@ -682,6 +693,10 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } result.text = tr::lng_action_topic_renamed( tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, lt_title, std::move(title), Ui::Text::WithEntities); @@ -996,7 +1011,7 @@ HistoryService::PreparedText HistoryService::preparePinnedText() { original = Ui::Text::Mid(original, 0, cutAt).append( Ui::kQEllipsis); } - original = Ui::Text::Wrapped( + original = Ui::Text::Link( Ui::Text::Filtered( std::move(original), { @@ -1005,8 +1020,7 @@ HistoryService::PreparedText HistoryService::preparePinnedText() { EntityType::Italic, EntityType::CustomEmoji, }), - EntityType::CustomUrl, - Ui::Text::Link({}, 2).entities.front().data()); + 2); result.text = tr::lng_action_pinned_message( tr::now, lt_from, @@ -1478,23 +1492,44 @@ void HistoryService::createFromMtp(const MTPDmessage &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) { UpdateComponents(HistoryServicePinned::Bit()); } else if (type == mtpc_messageActionTopicCreate || type == mtpc_messageActionTopicEdit) { UpdateComponents(HistoryServiceTopicInfo::Bit()); - Get()->topicPost = true; + const auto info = Get(); + 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) { setupChatThemeChange(); } else if (type == mtpc_messageActionSetMessagesTTL) { setupTTLChange(); } else if (type == mtpc_messageActionGameScore) { - const auto &data = message.vaction().c_messageActionGameScore(); + const auto &data = action.c_messageActionGameScore(); UpdateComponents(HistoryServiceGameScore::Bit()); Get()->score = data.vscore().v; } else if (type == mtpc_messageActionPaymentSent) { - const auto &data = message.vaction().c_messageActionPaymentSent(); + const auto &data = action.c_messageActionPaymentSent(); UpdateComponents(HistoryServicePayment::Bit()); const auto amount = data.vtotal_amount().v; const auto currency = qs(data.vcurrency()); @@ -1521,10 +1556,10 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) { || type == mtpc_messageActionGroupCallScheduled) { const auto started = (type == mtpc_messageActionGroupCall); const auto &callData = started - ? message.vaction().c_messageActionGroupCall().vcall() - : message.vaction().c_messageActionGroupCallScheduled().vcall(); + ? action.c_messageActionGroupCall().vcall() + : action.c_messageActionGroupCallScheduled().vcall(); const auto duration = started - ? message.vaction().c_messageActionGroupCall().vduration() + ? action.c_messageActionGroupCall().vduration() : tl::conditional(); if (duration) { RemoveComponents(HistoryServiceOngoingCall::Bit()); @@ -1535,7 +1570,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) { call->link = GroupCallClickHandler(history()->peer, call->id); } } 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 peer = history()->peer; const auto has = PeerHasThisCall(peer, id); @@ -1586,7 +1621,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) { } }); } - setMessageByAction(message.vaction()); + setMessageByAction(action); } const std::vector &HistoryService::customTextLinks() const { diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index 4a596ae6e..b293dbb65 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -30,6 +30,12 @@ struct HistoryServicePinned struct HistoryServiceTopicInfo : public RuntimeComponent , public HistoryServiceDependentData { + QString title; + DocumentId iconId = 0; + bool closed = false; + bool reopened = false; + bool reiconed = false; + bool renamed = false; }; struct HistoryServiceGameScore diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 2f611d372..d71b34a95 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -633,7 +633,7 @@ bool AddViewRepliesAction( : item->replyToTop(); const auto highlightId = topicRootId ? item->id : 0; const auto phrase = topicRootId - ? u"View in Thread"_q // #TODO lang-forum + ? tr::lng_replies_view_topic(tr::now) : (repliesCount > 0) ? tr::lng_replies_view( tr::now, diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index d6a307feb..f93aed046 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -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_spoiler_click_handler.h" #include "history/history.h" +#include "history/history_service.h" #include "base/unixtime.h" #include "core/application.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/toasts/common_toasts.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" #include "ui/item_text_options.h" #include "ui/painter.h" #include "data/data_session.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_sponsored_messages.h" #include "data/data_message_reactions.h" @@ -657,6 +661,141 @@ int Element::textHeightFor(int textWidth) { return _textHeight; } +auto Element::contextDependentServiceText() -> TextWithLinks { + if (_delegate->elementContext() == Context::Replies) { + return {}; + } + const auto item = data(); + const auto info = item->Get(); + 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 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())), + 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() { const auto item = data(); const auto &text = item->_text; @@ -668,13 +807,20 @@ void Element::validateText() { .customEmojiRepaint = [=] { customEmojiRepaint(); }, }; 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( st::serviceTextStyle, - text, + markedText, Ui::ItemTextServiceOptions(), context); auto linkIndex = 0; - for (const auto &link : item->customTextLinks()) { + for (const auto &link : customLinks) { // Link indices start with 1. _text.setLink(++linkIndex, link); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index d98f6e171..b777d1800 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -515,6 +515,12 @@ private: void refreshMedia(Element *replacing); + struct TextWithLinks { + TextWithEntities text; + std::vector links; + }; + [[nodiscard]] TextWithLinks contextDependentServiceText(); + const not_null _delegate; const not_null _data; HistoryBlock *_block = nullptr; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c199a1722..7f1dd3c35 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c199a1722fae72e254753f3095444a3c82a2a704 +Subproject commit 7f1dd3c351f534d0564c41376176eb0cd1be4184