Unify channel links and usernames clicks.

This commit is contained in:
John Preston 2024-10-10 13:21:19 +04:00
parent 3cb33f0825
commit af728e82fc
7 changed files with 138 additions and 48 deletions

View file

@ -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";

View file

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

View file

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

View file

@ -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<Ui::RpWidget> 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<Ui::RpWidget> 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<Ui::IconButton>(
usernameLine.text->parentWidget(),
@ -1215,6 +1250,8 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
addToLink);
linkLine.text->overrideLinkClickHandler(linkCallback);
linkLine.subtext->overrideLinkClickHandler(linkCallback);
linkLine.text->setContextMenuHook(lnkHook);
linkLine.subtext->setContextMenuHook(lnkHook);
{
const auto qr = Ui::CreateChild<Ui::IconButton>(
linkLine.text->parentWidget(),

View file

@ -166,13 +166,21 @@ rpl::producer<TextWithEntities> UsernameValue(
}) | Ui::Text::ToWithEntities();
}
QString UsernameUrl(not_null<PeerData*> 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<PeerData*> 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<std::vector<TextWithEntities>> UsernamesValue(
@ -243,7 +251,7 @@ rpl::producer<LinkWithUrl> LinkValue(not_null<PeerData*> peer, bool primary) {
: peer->session().createInternalLinkFull(username)),
.url = (username.isEmpty()
? QString()
: UsernameUrl(peer, username)),
: UsernameUrl(peer, username, true)),
};
});
}

View file

@ -63,7 +63,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
not_null<PeerData*> peer);
[[nodiscard]] QString UsernameUrl(
not_null<PeerData*> peer,
const QString &username);
const QString &username,
bool link = false);
[[nodiscard]] TextWithEntities AboutWithEntities(
not_null<PeerData*> peer,
const QString &value);

View file

@ -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<Ui::FlatLabel>(
@ -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);