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_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?";

View File

@ -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 };
});

View File

@ -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,

View File

@ -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<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) {
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<HistoryServiceGameScore>()->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<MTPint>();
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<ClickHandlerPtr> &HistoryService::customTextLinks() const {

View File

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

View File

@ -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,

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_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<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() {
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);
}

View File

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

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