Display topic name in chats list.

This commit is contained in:
John Preston 2022-11-02 16:39:13 +04:00
parent fdf4129e5e
commit 34a2c5c8ce
28 changed files with 223 additions and 172 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

View file

@ -2049,6 +2049,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dialogs_text_from_wrapped" = "{from}:";
"lng_dialogs_text_media" = "{media_part} {caption}";
"lng_dialogs_text_media_wrapped" = "{media},";
"lng_dialogs_text_from_in_topic" = "{from} {topic}";
"lng_dialogs_show_all_chats" = "Show all chats";
"lng_dialogs_hide_muted_chats" = "Hide muted chats";
"lng_dialogs_skip_archive_in_search" = "Skip results from archive";

View file

@ -21,9 +21,7 @@ using namespace TextUtilities;
[[nodiscard]] QString CustomEmojiEntityData(
const MTPDmessageEntityCustomEmoji &data) {
return Data::SerializeCustomEmojiId({
.id = data.vdocument_id().v,
});
return Data::SerializeCustomEmojiId(data.vdocument_id().v);
}
[[nodiscard]] std::optional<MTPMessageEntity> CustomEmojiEntity(
@ -31,13 +29,13 @@ using namespace TextUtilities;
MTPint length,
const QString &data) {
const auto parsed = Data::ParseCustomEmojiData(data);
if (!parsed.id) {
if (!parsed) {
return {};
}
return MTP_messageEntityCustomEmoji(
offset,
length,
MTP_long(parsed.id));
MTP_long(parsed));
}
[[nodiscard]] std::optional<MTPMessageEntity> MentionNameEntity(

View file

@ -87,11 +87,11 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
} else if (Ui::InputField::IsCustomEmojiLink(tag)) {
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
const auto emoji = Data::ParseCustomEmojiData(data);
if (!emoji.id) {
if (!emoji) {
i = all.erase(i);
continue;
} else if (!_session->premium()) {
const auto document = _session->data().document(emoji.id);
const auto document = _session->data().document(emoji);
if (document->isPremiumEmoji()) {
if (!_allowPremiumEmoji
|| premiumSkipped

View file

@ -225,6 +225,13 @@ void Forum::applyReceivedTopics(
).first->second.get()
: i->second.get();
raw->applyTopic(data);
if (creating) {
if (const auto last = _history->chatListMessage()
; last && last->topicRootId() == rootId) {
_history->lastItemDialogsView().itemInvalidated(last);
_history->updateChatListEntry();
}
}
if (callback) {
callback(raw);
}

View file

@ -139,6 +139,14 @@ QImage ForumTopicIconFrame(
return background;
}
TextWithEntities ForumTopicIconWithTitle(
DocumentId iconId,
const QString &title) {
return iconId
? Data::SingleCustomEmoji(iconId).append(title)
: TextWithEntities{ title };
}
ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
: Thread(&forum->history()->owner(), Type::ForumTopic)
, _forum(forum)
@ -570,6 +578,10 @@ QString ForumTopic::title() const {
return _title;
}
TextWithEntities ForumTopic::titleWithIcon() const {
return ForumTopicIconWithTitle(_iconId, _title);
}
void ForumTopic::applyTitle(const QString &title) {
if (_title == title) {
return;

View file

@ -44,6 +44,9 @@ class Forum;
int32 colorId,
const QString &title,
const style::ForumTopicIcon &st);
[[nodiscard]] TextWithEntities ForumTopicIconWithTitle(
DocumentId iconId,
const QString &title);
class ForumTopic final : public Thread {
public:
@ -107,6 +110,7 @@ public:
[[nodiscard]] MsgId lastKnownServerMessageId() const;
[[nodiscard]] QString title() const;
[[nodiscard]] TextWithEntities titleWithIcon() const;
void applyTitle(const QString &title);
[[nodiscard]] DocumentId iconId() const;
void applyIconId(DocumentId iconId);

View file

@ -15,9 +15,7 @@ QString ReactionEntityData(const ReactionId &id) {
if (id.empty()) {
return {};
} else if (const auto custom = id.custom()) {
return SerializeCustomEmojiId({
.id = custom,
});
return SerializeCustomEmojiId(custom);
}
return u"default:"_q + id.emoji();
}

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h"
#include "ui/widgets/input_fields.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "ui/ui_utility.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
@ -91,7 +92,7 @@ class CustomEmojiLoader final
public:
CustomEmojiLoader(
not_null<Session*> owner,
const CustomEmojiId id,
DocumentId id,
SizeTag tag,
int sizeOverride);
CustomEmojiLoader(
@ -144,7 +145,7 @@ private:
[[nodiscard]] static std::variant<Resolve, Lookup, Load> InitialState(
not_null<Session*> owner,
const CustomEmojiId &id);
DocumentId id);
std::variant<Resolve, Lookup, Load> _state;
ushort _sizeOverride = 0;
@ -154,7 +155,7 @@ private:
CustomEmojiLoader::CustomEmojiLoader(
not_null<Session*> owner,
const CustomEmojiId id,
DocumentId id,
SizeTag tag,
int sizeOverride)
: _state(InitialState(owner, id))
@ -368,9 +369,9 @@ void CustomEmojiLoader::check() {
auto CustomEmojiLoader::InitialState(
not_null<Session*> owner,
const CustomEmojiId &id)
DocumentId id)
-> std::variant<Resolve, Lookup, Load> {
const auto document = owner->document(id.id);
const auto document = owner->document(id);
if (document->sticker()) {
return Lookup{ document };
}
@ -504,8 +505,8 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
SizeTag tag,
int sizeOverride) {
const auto parsed = ParseCustomEmojiData(data);
return parsed.id
? create(parsed.id, std::move(update), tag, sizeOverride)
return parsed
? create(parsed, std::move(update), tag, sizeOverride)
: nullptr;
}
@ -532,7 +533,7 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
void CustomEmojiManager::resolve(
QStringView data,
not_null<Listener*> listener) {
resolve(ParseCustomEmojiData(data).id, listener);
resolve(ParseCustomEmojiData(data), listener);
}
void CustomEmojiManager::resolve(
@ -619,7 +620,7 @@ auto CustomEmojiManager::createLoaderWithSetId(
) -> LoaderWithSetId {
auto result = std::make_unique<CustomEmojiLoader>(
_owner,
CustomEmojiId{ .id = documentId },
documentId,
tag,
sizeOverride);
if (const auto document = result->document()) {
@ -882,18 +883,24 @@ int FrameSizeFromTag(SizeTag tag) {
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
}
QString SerializeCustomEmojiId(const CustomEmojiId &id) {
return QString::number(id.id);
QString SerializeCustomEmojiId(DocumentId id) {
return QString::number(id);
}
QString SerializeCustomEmojiId(not_null<DocumentData*> document) {
return SerializeCustomEmojiId({
.id = document->id,
});
return SerializeCustomEmojiId(document->id);
}
CustomEmojiId ParseCustomEmojiData(QStringView data) {
return { .id = data.toULongLong() };
DocumentId ParseCustomEmojiData(QStringView data) {
return data.toULongLong();
}
TextWithEntities SingleCustomEmoji(DocumentId id) {
return Ui::Text::SingleCustomEmoji(SerializeCustomEmojiId(id));
}
TextWithEntities SingleCustomEmoji(not_null<DocumentData*> document) {
return SingleCustomEmoji(document->id);
}
bool AllowEmojiWithoutPremium(not_null<PeerData*> peer) {

View file

@ -21,10 +21,6 @@ namespace Data {
class Session;
class CustomEmojiLoader;
struct CustomEmojiId {
DocumentId id = 0;
};
enum class CustomEmojiSizeTag : uchar {
Normal,
Large,
@ -176,10 +172,14 @@ private:
[[nodiscard]] int FrameSizeFromTag(CustomEmojiManager::SizeTag tag);
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id);
[[nodiscard]] QString SerializeCustomEmojiId(DocumentId id);
[[nodiscard]] QString SerializeCustomEmojiId(
not_null<DocumentData*> document);
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data);
[[nodiscard]] DocumentId ParseCustomEmojiData(QStringView data);
[[nodiscard]] TextWithEntities SingleCustomEmoji(DocumentId id);
[[nodiscard]] TextWithEntities SingleCustomEmoji(
not_null<DocumentData*> document);
[[nodiscard]] bool AllowEmojiWithoutPremium(not_null<PeerData*> peer);

View file

@ -69,7 +69,7 @@ defaultDialogRow: DialogRow {
nameLeft: 68px;
nameTop: 10px;
textLeft: 68px;
textTop: 35px;
textTop: 34px;
}
dialogsOnlineBadgeStroke: 2px;
@ -460,3 +460,7 @@ chooseTopicListItem: PeerListItem(defaultPeerListItem) {
chooseTopicList: PeerList(defaultPeerList) {
item: chooseTopicListItem;
}
dialogsTopicArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgService }};
dialogsTopicArrowSkip: 13px;
dialogsTopicArrowTop: 4px;

View file

@ -921,7 +921,7 @@ void RowPainter::Paint(
view->prepare(
item,
[=] { entry->updateChatListEntry(); },
{});
{ .ignoreTopic = (!history || !peer->isForum()) });
}
view->paint(p, rect, context);
}

View file

@ -18,19 +18,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "core/ui_integration.h"
#include "lang/lang_keys.h"
#include "lang/lang_text_entity.h"
#include "styles/style_dialogs.h"
namespace {
template <ushort kTag>
struct TextWithTagOffset {
TextWithTagOffset(QString text) : text(text) {
TextWithTagOffset(TextWithEntities text) : text(std::move(text)) {
}
TextWithTagOffset(QString text) : text({ std::move(text) }) {
}
static TextWithTagOffset FromString(const QString &text) {
return { text };
return { { text } };
}
QString text;
TextWithEntities text;
int offset = -1;
};
@ -52,12 +55,12 @@ TextWithTagOffset<kTag> ReplaceTag<TextWithTagOffset<kTag>>::Call(
ushort tag,
const TextWithTagOffset<kTag> &replacement) {
const auto replacementPosition = FindTagReplacementPosition(
original.text,
original.text.text,
tag);
if (replacementPosition < 0) {
return std::move(original);
}
original.text = ReplaceTag<QString>::Replace(
original.text = ReplaceTag<TextWithEntities>::Replace(
std::move(original.text),
replacement.text,
replacementPosition);
@ -65,7 +68,8 @@ TextWithTagOffset<kTag> ReplaceTag<TextWithTagOffset<kTag>>::Call(
original.offset = replacementPosition;
} else if (original.offset > replacementPosition) {
constexpr auto kReplaceCommandLength = 4;
original.offset += replacement.text.size() - kReplaceCommandLength;
const auto replacementSize = replacement.text.text.size();
original.offset += replacementSize - kReplaceCommandLength;
}
return std::move(original);
}
@ -105,6 +109,7 @@ struct MessageView::LoadingContext {
MessageView::MessageView()
: _senderCache(st::dialogsTextWidthMin)
, _topicCache(st::dialogsTextWidthMin)
, _textCache(st::dialogsTextWidthMin) {
}
@ -130,28 +135,46 @@ void MessageView::prepare(
ToPreviewOptions options) {
options.existing = &_imagesCache;
auto preview = item->toPreview(options);
if (!preview.images.empty() && preview.imagesInTextPosition > 0) {
auto sender = ::Ui::Text::Mid(
preview.text,
0,
preview.imagesInTextPosition);
TextUtilities::Trim(sender);
_senderCache.setMarkedText(
st::dialogsTextStyle,
std::move(sender),
DialogTextOptions());
preview.text = ::Ui::Text::Mid(
preview.text,
preview.imagesInTextPosition);
} else {
_senderCache = { st::dialogsTextWidthMin };
}
TextUtilities::Trim(preview.text);
const auto hasImages = !preview.images.empty();
const auto hasArrow = (preview.arrowInTextPosition > 0)
&& (preview.imagesInTextPosition > preview.arrowInTextPosition);
const auto history = item->history();
const auto context = Core::MarkedTextContext{
.session = &history->session(),
.customEmojiRepaint = customEmojiRepaint,
};
const auto senderTill = (preview.arrowInTextPosition > 0)
? preview.arrowInTextPosition
: preview.imagesInTextPosition;
if ((hasImages || hasArrow) && senderTill > 0) {
auto sender = Text::Mid(preview.text, 0, senderTill);
TextUtilities::Trim(sender);
_senderCache.setMarkedText(
st::dialogsTextStyle,
std::move(sender),
DialogTextOptions());
const auto topicTill = preview.imagesInTextPosition;
if (hasArrow && hasImages) {
auto topic = Text::Mid(
preview.text,
senderTill,
topicTill - senderTill);
TextUtilities::Trim(topic);
_topicCache.setMarkedText(
st::dialogsTextStyle,
std::move(topic),
DialogTextOptions(),
context);
preview.text = Text::Mid(preview.text, topicTill);
} else {
preview.text = Text::Mid(preview.text, senderTill);
_topicCache = { st::dialogsTextWidthMin };
}
} else {
_topicCache = { st::dialogsTextWidthMin };
_senderCache = { st::dialogsTextWidthMin };
}
TextUtilities::Trim(preview.text);
_textCache.setMarkedText(
st::dialogsTextStyle,
DialogsPreviewText(std::move(preview.text)),
@ -193,16 +216,46 @@ void MessageView::paint(
: st::dialogsTextPalette);
auto rect = geometry;
const auto lines = rect.height() / st::dialogsTextFont->height;
if (!_senderCache.isEmpty()) {
_senderCache.draw(p, {
.position = rect.topLeft(),
.availableWidth = rect.width(),
.palette = palette,
.elisionLines = rect.height() / st::dialogsTextFont->height,
.elisionLines = lines,
});
const auto skip = st::dialogsMiniPreviewSkip
+ st::dialogsMiniPreviewRight;
rect.setLeft(rect.x() + _senderCache.maxWidth() + skip);
rect.setLeft(rect.x() + _senderCache.maxWidth());
if (!_topicCache.isEmpty() || _imagesCache.empty()) {
const auto skip = st::dialogsTopicArrowSkip;
if (rect.width() >= skip) {
const auto &icon = st::dialogsTopicArrow;
icon.paint(
p,
rect.x() + (skip - icon.width()) / 2,
rect.y() + st::dialogsTopicArrowTop,
geometry.width());
}
rect.setLeft(rect.x() + skip);
}
if (!_topicCache.isEmpty()) {
if (!rect.isEmpty()) {
_topicCache.draw(p, {
.position = rect.topLeft(),
.availableWidth = rect.width(),
.palette = palette,
.spoiler = Text::DefaultSpoilerCache(),
.now = context.now,
.paused = context.paused,
.elisionLines = lines,
});
}
rect.setLeft(rect.x() + _topicCache.maxWidth());
}
if (!_imagesCache.empty()) {
const auto skip = st::dialogsMiniPreviewSkip
+ st::dialogsMiniPreviewRight;
rect.setLeft(rect.x() + skip);
}
}
for (const auto &image : _imagesCache) {
if (rect.width() < st::dialogsMiniPreview) {
@ -229,30 +282,48 @@ void MessageView::paint(
.spoiler = Text::DefaultSpoilerCache(),
.now = context.now,
.paused = context.paused,
.elisionLines = rect.height() / st::dialogsTextFont->height,
.elisionLines = lines,
});
}
HistoryView::ItemPreview PreviewWithSender(
HistoryView::ItemPreview &&preview,
const TextWithEntities &sender) {
const auto textWithOffset = tr::lng_dialogs_text_with_from(
const QString &sender,
TextWithEntities topic) {
auto senderWithOffset = topic.empty()
? TextWithTagOffset<lt_from>::FromString(sender)
: tr::lng_dialogs_text_from_in_topic(
tr::now,
lt_from,
{ sender },
lt_topic,
std::move(topic),
TextWithTagOffset<lt_from>::FromString);
auto wrappedWithOffset = tr::lng_dialogs_text_from_wrapped(
tr::now,
lt_from,
std::move(senderWithOffset.text),
TextWithTagOffset<lt_from>::FromString);
const auto wrappedSize = wrappedWithOffset.text.text.size();
auto fullWithOffset = tr::lng_dialogs_text_with_from(
tr::now,
lt_from_part,
sender.text,
lt_message,
preview.text.text,
TextWithTagOffset<lt_from_part>::FromString);
preview.text = tr::lng_dialogs_text_with_from(
tr::now,
lt_from_part,
sender,
Ui::Text::PlainLink(std::move(wrappedWithOffset.text)),
lt_message,
std::move(preview.text),
Ui::Text::WithEntities);
preview.imagesInTextPosition = (textWithOffset.offset < 0)
TextWithTagOffset<lt_from_part>::FromString);
preview.text = std::move(fullWithOffset.text);
preview.arrowInTextPosition = (fullWithOffset.offset < 0
|| wrappedWithOffset.offset < 0
|| senderWithOffset.offset < 0)
? -1
: (fullWithOffset.offset
+ wrappedWithOffset.offset
+ senderWithOffset.offset
+ sender.size());
preview.imagesInTextPosition = (fullWithOffset.offset < 0)
? 0
: textWithOffset.offset + sender.text.size();
: (fullWithOffset.offset + wrappedSize);
return std::move(preview);
}

View file

@ -57,6 +57,7 @@ private:
mutable const HistoryItem *_textCachedFor = nullptr;
mutable Text::String _senderCache;
mutable Text::String _topicCache;
mutable Text::String _textCache;
mutable std::vector<ItemPreviewImage> _imagesCache;
mutable std::unique_ptr<LoadingContext> _loadingContext;
@ -65,6 +66,7 @@ private:
[[nodiscard]] HistoryView::ItemPreview PreviewWithSender(
HistoryView::ItemPreview &&preview,
const TextWithEntities &sender);
const QString &sender,
TextWithEntities topic);
} // namespace Dialogs::Ui

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "data/data_channel.h"
#include "data/data_file_origin.h"
#include "data/data_forum_topic.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_message_reaction_id.h"
@ -624,23 +625,10 @@ TextWithEntities GenerateDefaultBannedRightsChangeText(
not_null<ChannelData*> channel,
const MTPForumTopic &topic) {
return topic.match([&](const MTPDforumTopic &data) {
const auto wrapIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({ .id = id }))
},
};
};
auto result = (data.vicon_emoji_id() && data.vicon_emoji_id()->v)
? wrapIcon(data.vicon_emoji_id()->v)
: TextWithEntities();
result.append(qs(data.vtitle()));
return Ui::Text::Link(
std::move(result),
Data::ForumTopicIconWithTitle(
data.vicon_emoji_id().value_or_empty(),
qs(data.vtitle())),
u"internal:url:https://t.me/c/%1/%2"_q.arg(
peerToChannel(channel->id).bare).arg(
data.vid().v));

View file

@ -1365,9 +1365,11 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
if (!sender) {
return result;
}
const auto fromWrapped = Ui::Text::PlainLink(
tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, *sender));
return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped);
const auto topic = options.ignoreTopic ? nullptr : this->topic();
return Dialogs::Ui::PreviewWithSender(
std::move(result),
*sender,
topic ? topic->titleWithIcon() : TextWithEntities());
}
TextWithEntities HistoryItem::inReplyText() const {

View file

@ -636,33 +636,19 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result;
};
const auto wrapTopicIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({ .id = id }))
},
};
};
auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) {
auto result = PreparedText{};
auto title = TextWithEntities{
qs(action.vtitle())
};
if (const auto icon = action.vicon_emoji_id().value_or_empty()) {
title = wrapTopicIcon(icon).append(' ').append(std::move(title));
}
const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
.arg(peerToChannel(history()->peer->id).bare)
.arg(id.bare);
result.text = tr::lng_action_topic_created(
tr::now,
lt_topic,
Ui::Text::Link(std::move(title), topicUrl),
Ui::Text::Link(
Data::ForumTopicIconWithTitle(
action.vicon_emoji_id().value_or_empty(),
qs(action.vtitle())),
topicUrl),
Ui::Text::WithEntities);
return result;
};
@ -684,7 +670,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
lt_link,
{ tr::lng_action_topic_placeholder(tr::now) },
lt_emoji,
wrapTopicIcon(iconId),
Data::SingleCustomEmoji(iconId),
Ui::Text::WithEntities);
} else {
result.links.push_back(fromLink());
@ -699,14 +685,6 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
}
} else {
result.links.push_back(fromLink());
auto title = TextWithEntities{
qs(*action.vtitle())
};
if (const auto icon = action.vicon_emoji_id().value_or_empty()) {
title = wrapTopicIcon(icon)
.append(' ')
.append(std::move(title));
}
result.text = tr::lng_action_topic_renamed(
tr::now,
lt_from,
@ -714,7 +692,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
lt_link,
{ tr::lng_action_topic_placeholder(tr::now) },
lt_title,
std::move(title),
Data::ForumTopicIconWithTitle(
action.vicon_emoji_id().value_or_empty(),
qs(*action.vtitle())),
Ui::Text::WithEntities);
}
if (result.text.empty()) {

View file

@ -1311,7 +1311,7 @@ std::vector<StickerSetIdentifier> CollectEmojiPacks(
for (const auto &entity : item->originalText().entities) {
if (entity.type() == EntityType::CustomEmoji) {
const auto data = Data::ParseCustomEmojiData(entity.data());
push(data.id);
push(data);
}
}
break;

View file

@ -690,17 +690,6 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
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/%2"_q
.arg(peerToChannel(peerId).bare)
.arg(topicRootId.bare);
@ -715,11 +704,9 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
const auto wrapTopic = [&](
const QString &title,
std::optional<DocumentId> iconId) {
auto result = TextWithEntities{ title };
auto full = (iconId && *iconId)
? wrapIcon(*iconId).append(' ').append(std::move(result))
: result;
return Ui::Text::Link(std::move(full), topicUrl);
return Ui::Text::Link(
Data::ForumTopicIconWithTitle(iconId.value_or(0), title),
topicUrl);
};
const auto wrapParentTopic = [&] {
const auto forum = history()->asForum();
@ -783,7 +770,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
lt_link,
placeholderLink(),
lt_emoji,
wrapIcon(iconId),
Data::SingleCustomEmoji(iconId),
Ui::Text::WithEntities),
{ from->createOpenLink() },
};

View file

@ -21,6 +21,7 @@ struct ItemPreviewImage {
struct ItemPreview {
TextWithEntities text;
std::vector<ItemPreviewImage> images;
int arrowInTextPosition = -1;
int imagesInTextPosition = 0;
std::any loadingContext;
};
@ -31,7 +32,7 @@ struct ToPreviewOptions {
bool hideCaption = false;
bool generateImages = true;
bool ignoreGroup = false;
bool ignoreSpoilers = false;
bool ignoreTopic = true;
};
} // namespace HistoryView

View file

@ -92,7 +92,7 @@ CustomEmoji::CustomEmoji(
tag));
} else {
const auto &data = element.entityData;
const auto id = Data::ParseCustomEmojiData(data).id;
const auto id = Data::ParseCustomEmojiData(data);
const auto document = owner->document(id);
if (document->sticker()) {
_lines.back().push_back(createStickerPart(document));

View file

@ -16,25 +16,28 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
if (replacementPosition < 0) {
return std::move(original);
}
return Replace(std::move(original), replacement, replacementPosition);
}
TextWithEntities ReplaceTag<TextWithEntities>::Replace(TextWithEntities &&original, const TextWithEntities &replacement, int start) {
auto result = TextWithEntities();
result.text = ReplaceTag<QString>::Replace(std::move(original.text), replacement.text, replacementPosition);
result.text = ReplaceTag<QString>::Replace(std::move(original.text), replacement.text, start);
auto originalEntitiesCount = original.entities.size();
auto replacementEntitiesCount = replacement.entities.size();
if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) {
result.entities.reserve(originalEntitiesCount + replacementEntitiesCount);
auto replacementEnd = replacementPosition + int(replacement.text.size());
auto replacementEnd = start + int(replacement.text.size());
auto replacementEntity = replacement.entities.cbegin();
auto addReplacementEntitiesUntil = [&replacementEntity, &replacement, &result, replacementPosition, replacementEnd](int untilPosition) {
auto addReplacementEntitiesUntil = [&](int untilPosition) {
while (replacementEntity != replacement.entities.cend()) {
auto newOffset = replacementPosition + replacementEntity->offset();
auto newOffset = start + replacementEntity->offset();
if (newOffset >= untilPosition) {
return;
}
auto newEnd = newOffset + replacementEntity->length();
newOffset = std::clamp(newOffset, replacementPosition, replacementEnd);
newEnd = std::clamp(newEnd, replacementPosition, replacementEnd);
newOffset = std::clamp(newOffset, start, replacementEnd);
newEnd = std::clamp(newEnd, start, replacementEnd);
if (auto newLength = newEnd - newOffset) {
result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() });
}
@ -46,10 +49,10 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
// Transform the entity by the replacement.
auto offset = entity.offset();
auto end = offset + entity.length();
if (offset > replacementPosition) {
if (offset > start) {
offset = offset + replacement.text.size() - kTagReplacementSize;
}
if (end > replacementPosition) {
if (end > start) {
end = end + replacement.text.size() - kTagReplacementSize;
}
offset = std::clamp(offset, 0, int(result.text.size()));

View file

@ -27,6 +27,7 @@ struct ReplaceTag;
template <>
struct ReplaceTag<TextWithEntities> {
static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement);
static TextWithEntities Replace(TextWithEntities &&original, const TextWithEntities &replacement, int start);
};

View file

@ -848,7 +848,7 @@ TextWithEntities Manager::ComposeReactionEmoji(
EntityType::CustomEmoji,
0,
text.size(),
Data::SerializeCustomEmojiId(Data::CustomEmojiId{ id }))
Data::SerializeCustomEmojiId(id))
}
};
}

View file

@ -968,24 +968,9 @@ void Notification::updateNotifyDisplay() {
const auto topicWithChat = [&]() -> TextWithEntities {
const auto name = _history->peer->name();
const auto wrapIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({.id = id }))
},
};
};
if (!_topic) {
return { name };
}
auto start = _topic->iconId()
? wrapIcon(_topic->iconId())
: TextWithEntities();
return start.append(_topic->title() + u" ("_q + name + ')');
return _topic
? _topic->titleWithIcon().append(u" ("_q + name + ')')
: TextWithEntities{ name };
};
auto title = options.hideNameAndPhoto
? TextWithEntities{ u"Telegram Desktop"_q }

@ -1 +1 @@
Subproject commit c3616927ebfcbe98375189be87821bea202d9587
Subproject commit 4539d0bab4ffc335de79b77ee9632f6a7b59d48b