From af728e82fcf51850678699f959bd278c25dcf9f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 10 Oct 2024 13:21:19 +0400 Subject: [PATCH] Unify channel links and usernames clicks. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/core/application.cpp | 4 +- .../SourceFiles/core/local_url_handlers.cpp | 40 +++++++- .../info/profile/info_profile_actions.cpp | 93 +++++++++++++------ .../info/profile/info_profile_values.cpp | 24 +++-- .../info/profile/info_profile_values.h | 3 +- .../ui/boxes/collectible_info_box.cpp | 20 ++-- 7 files changed, 138 insertions(+), 48 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a57fc8b1b..20f7825cb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -453,6 +453,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_username_app_not_found" = "Bot application not found."; "lng_username_link" = "This link opens a chat with you:"; "lng_username_copied" = "Link copied to clipboard."; +"lng_username_text_copied" = "Username copied to clipboard."; "lng_usernames_edit" = "click to edit"; "lng_usernames_active" = "active"; @@ -487,6 +488,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}"; "lng_collectible_phone_copy" = "Copy Phone Number"; "lng_collectible_learn_more" = "Learn More"; +"lng_collectible_phone_copied" = "Phone number copied to clipboard."; "lng_settings_section_info" = "Info"; diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index b91813e16..c12747322 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -1163,9 +1163,9 @@ bool Application::openCustomUrl( return false; } static const auto kTagExp = QRegularExpression( - u"\\~[a-zA-Z0-9_\\-]+\\~:"_q); + u"^\\~[a-zA-Z0-9_\\-]+\\~:"_q); auto skip = protocol.size(); - const auto match = kTagExp.match(urlTrimmed, skip); + const auto match = kTagExp.match(base::StringViewMid(urlTrimmed, skip)); if (match.hasMatch()) { skip += match.capturedLength(); } diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 910d291e9..c07832e9a 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -785,9 +785,9 @@ bool CopyPeerId( Window::SessionController *controller, const Match &match, const QVariant &context) { - TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) }); + TextUtilities::SetClipboardText({ match->captured(1) }); if (controller) { - controller->showToast(tr::lng_text_copied(tr::now)); + controller->showToast(u"ID copied to clipboard."_q); } return true; } @@ -926,6 +926,34 @@ bool ShowCollectibleUsername( return true; } +bool CopyUsernameLink( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto username = match->captured(1); + TextUtilities::SetClipboardText({ + controller->session().createInternalLinkFull(username) + }); + controller->showToast(tr::lng_username_copied(tr::now)); + return true; +} + +bool CopyUsername( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto username = match->captured(1); + TextUtilities::SetClipboardText({ '@' + username }); + controller->showToast(tr::lng_username_text_copied(tr::now)); + return true; +} + bool ShowStarsExamples( Window::SessionController *controller, const Match &match, @@ -1391,6 +1419,14 @@ const std::vector &InternalUrlHandlers() { u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q, ShowCollectibleUsername, }, + { + u"^username_link/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q, + CopyUsernameLink, + }, + { + u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q, + CopyUsername, + }, { u"^stars_examples$"_q, ShowStarsExamples, diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index f23cf8c10..55feed317 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -147,8 +147,10 @@ base::options::toggle ShowPeerIdBelowAbout({ + addToLink; } if (!link.isEmpty()) { + TextUtilities::SetClipboardText({ link }); if (const auto strong = weak.get()) { - FastShareLink(strong, link); + strong->showToast( + tr::lng_channel_public_link_copied(tr::now)); } } }; @@ -1074,8 +1076,65 @@ object_ptr DetailsFiller::setupInfo() { - button->width()); }, button->lifetime()); }; + const auto controller = _controller->parentController(); + const auto weak = base::make_weak(controller); + const auto peerIdRaw = QString::number(_peer->id.value); + const auto lnkHook = [=](Ui::FlatLabel::ContextMenuRequest request) { + const auto strong = weak.get(); + if (!strong || !request.link) { + return; + } + const auto url = request.link->url(); + if (url.startsWith(u"https://")) { + request.menu->addAction( + tr::lng_context_copy_link(tr::now), + [=] { + TextUtilities::SetClipboardText({ url }); + if (const auto strong = weak.get()) { + strong->showToast( + tr::lng_channel_public_link_copied(tr::now)); + } + }); + request.menu->addAction( + tr::lng_group_invite_share(tr::now), + [=] { + if (const auto strong = weak.get()) { + FastShareLink(strong, url); + } + }); + return; + } + static const auto kPrefix = QRegularExpression(u"^internal:" + "(collectible_username|username_link|username_regular)/" + "([a-zA-Z0-9\\-\\_\\.]+)@"_q); + const auto match = kPrefix.match(url); + if (!match.hasMatch()) { + return; + } + const auto username = match.captured(2); + const auto fullname = username + '@' + peerIdRaw; + const auto mentionLink = "internal:username_regular/" + fullname; + const auto linkLink = "internal:username_link/" + fullname; + const auto context = QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = weak, + }); + const auto session = &strong->session(); + const auto link = session->createInternalLinkFull(username); + request.menu->addAction( + tr::lng_context_copy_mention(tr::now), + [=] { Core::App().openInternalUrl(mentionLink, context); }); + request.menu->addAction( + tr::lng_context_copy_link(tr::now), + [=] { Core::App().openInternalUrl(linkLink, context); }); + request.menu->addAction( + tr::lng_group_invite_share(tr::now), + [=] { + if (const auto strong = weak.get()) { + FastShareLink(strong, link); + } + }); + }; if (const auto user = _peer->asUser()) { - const auto controller = _controller->parentController(); if (user->session().supportMode()) { addInfoLineGeneric( user->session().supportHelper().infoLabelValue(user), @@ -1113,34 +1172,10 @@ object_ptr DetailsFiller::setupInfo() { _peer, controller, QString()); - const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) { - if (!request.link) { - return; - } - const auto text = request.link->copyToClipboardContextItemText(); - if (text.isEmpty()) { - return; - } - const auto link = request.link->copyToClipboardText(); - request.menu->addAction( - text, - [=] { QGuiApplication::clipboard()->setText(link); }); - const auto last = link.lastIndexOf('/'); - if (last < 0) { - return; - } - const auto mention = '@' + link.mid(last + 1); - if (mention.size() < 2) { - return; - } - request.menu->addAction( - tr::lng_context_copy_mention(tr::now), - [=] { QGuiApplication::clipboard()->setText(mention); }); - }; usernameLine.text->overrideLinkClickHandler(callback); usernameLine.subtext->overrideLinkClickHandler(callback); - usernameLine.text->setContextMenuHook(hook); - usernameLine.subtext->setContextMenuHook(hook); + usernameLine.text->setContextMenuHook(lnkHook); + usernameLine.subtext->setContextMenuHook(lnkHook); const auto copyUsername = Ui::CreateChild( usernameLine.text->parentWidget(), @@ -1215,6 +1250,8 @@ object_ptr DetailsFiller::setupInfo() { addToLink); linkLine.text->overrideLinkClickHandler(linkCallback); linkLine.subtext->overrideLinkClickHandler(linkCallback); + linkLine.text->setContextMenuHook(lnkHook); + linkLine.subtext->setContextMenuHook(lnkHook); { const auto qr = Ui::CreateChild( linkLine.text->parentWidget(), diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index 624b488f0..fba586e0d 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -166,13 +166,21 @@ rpl::producer UsernameValue( }) | Ui::Text::ToWithEntities(); } -QString UsernameUrl(not_null peer, const QString &username) { - return peer->isUsernameEditable(username) - ? peer->session().createInternalLinkFull(username) - : (u"internal:collectible_username/"_q - + username - + "@" - + QString::number(peer->id.value)); +QString UsernameUrl( + not_null peer, + const QString &username, + bool link) { + const auto type = !peer->isUsernameEditable(username) + ? u"collectible_username"_q + : link + ? u"username_link"_q + : u"username_regular"_q; + return u"internal:"_q + + type + + u"/"_q + + username + + "@" + + QString::number(peer->id.value); } rpl::producer> UsernamesValue( @@ -243,7 +251,7 @@ rpl::producer LinkValue(not_null peer, bool primary) { : peer->session().createInternalLinkFull(username)), .url = (username.isEmpty() ? QString() - : UsernameUrl(peer, username)), + : UsernameUrl(peer, username, true)), }; }); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 981b70aba..6001bd980 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -63,7 +63,8 @@ rpl::producer> MigratedOrMeValue( not_null peer); [[nodiscard]] QString UsernameUrl( not_null peer, - const QString &username); + const QString &username, + bool link = false); [[nodiscard]] TextWithEntities AboutWithEntities( not_null peer, const QString &value); diff --git a/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp b/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp index 4498becad..d76d6802d 100644 --- a/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp @@ -187,12 +187,16 @@ void CollectibleInfoBox( lt_username, Ui::Text::Link(formatted), Ui::Text::WithEntities); - const auto copyCallback = [box, type, formatted, text = info.copyText] { - QGuiApplication::clipboard()->setText( - text.isEmpty() ? formatted : text); + const auto copyCallback = [box, type, formatted, text = info.copyText]( + bool copyLink) { + QGuiApplication::clipboard()->setText((text.isEmpty() || !copyLink) + ? formatted + : text); box->uiShow()->showToast((type == CollectibleType::Phone) - ? tr::lng_text_copied(tr::now) - : tr::lng_username_copied(tr::now)); + ? tr::lng_collectible_phone_copied(tr::now) + : copyLink + ? tr::lng_username_copied(tr::now) + : tr::lng_username_text_copied(tr::now)); }; box->addRow( object_ptr( @@ -201,7 +205,7 @@ void CollectibleInfoBox( st::collectibleHeader), st::collectibleHeaderPadding )->setClickHandlerFilter([copyCallback](const auto &...) { - copyCallback(); + copyCallback(false); return false; }); @@ -242,7 +246,9 @@ void CollectibleInfoBox( st::collectibleCopy); const auto copy = owned.data(); copy->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - copy->setClickedCallback(copyCallback); + copy->setClickedCallback([copyCallback] { + copyCallback(true); + }); box->addButton(std::move(owned)); box->setNoContentMargin(true);