mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Highlight YouTube video timestamps as external links.
This commit is contained in:
parent
ebded1b421
commit
f2e4a5a35a
12 changed files with 170 additions and 53 deletions
|
@ -65,6 +65,26 @@ bool UrlRequiresConfirmation(const QUrl &url) {
|
|||
RegExOption::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString HiddenUrlClickHandler::copyToClipboardText() const {
|
||||
return url().startsWith(qstr("internal:url:"))
|
||||
? url().mid(qstr("internal:url:").size())
|
||||
: url();
|
||||
}
|
||||
|
||||
QString HiddenUrlClickHandler::copyToClipboardContextItemText() const {
|
||||
return url().isEmpty()
|
||||
? QString()
|
||||
: !url().startsWith(qstr("internal:"))
|
||||
? UrlClickHandler::copyToClipboardContextItemText()
|
||||
: url().startsWith(qstr("internal:url:"))
|
||||
? UrlClickHandler::copyToClipboardContextItemText()
|
||||
: QString();
|
||||
}
|
||||
|
||||
QString HiddenUrlClickHandler::dragText() const {
|
||||
return HiddenUrlClickHandler::copyToClipboardText();
|
||||
}
|
||||
|
||||
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||
url = Core::TryConvertUrlToLocal(url);
|
||||
if (Core::InternalPassportLink(url)) {
|
||||
|
|
|
@ -39,11 +39,9 @@ class HiddenUrlClickHandler : public UrlClickHandler {
|
|||
public:
|
||||
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
|
||||
}
|
||||
QString copyToClipboardContextItemText() const override {
|
||||
return (url().isEmpty() || url().startsWith(qstr("internal:")))
|
||||
? QString()
|
||||
: UrlClickHandler::copyToClipboardContextItemText();
|
||||
}
|
||||
QString copyToClipboardText() const override;
|
||||
QString copyToClipboardContextItemText() const override;
|
||||
QString dragText() const override;
|
||||
|
||||
static void Open(QString url, QVariant context = {});
|
||||
void onClick(ClickContext context) const override {
|
||||
|
|
|
@ -474,6 +474,15 @@ bool ShowInviteLink(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool OpenExternalLink(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
return Ui::Integration::Instance().handleUrlClick(
|
||||
match->captured(1),
|
||||
context);
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
|
@ -698,6 +707,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
|||
qsl("^show_invite_link/?\\?link=([a-zA-Z0-9_\\+\\/\\=\\-]+)(&|$)"),
|
||||
ShowInviteLink
|
||||
},
|
||||
{
|
||||
qsl("^url:(.+)$"),
|
||||
OpenExternalLink
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
@ -224,7 +225,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
|
|||
bool HistoryMessageReply::updateData(
|
||||
not_null<HistoryMessage*> holder,
|
||||
bool force) {
|
||||
const auto guard = gsl::finally([&] { refreshReplyToDocument(); });
|
||||
const auto guard = gsl::finally([&] { refreshReplyToMedia(); });
|
||||
if (!force) {
|
||||
if (replyToMsg || !replyToMsgId) {
|
||||
return true;
|
||||
|
@ -291,7 +292,7 @@ void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
|
|||
replyToMsg = nullptr;
|
||||
}
|
||||
replyToMsgId = 0;
|
||||
refreshReplyToDocument();
|
||||
refreshReplyToMedia();
|
||||
}
|
||||
|
||||
bool HistoryMessageReply::isNameUpdated() const {
|
||||
|
@ -416,11 +417,14 @@ void HistoryMessageReply::paint(
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryMessageReply::refreshReplyToDocument() {
|
||||
void HistoryMessageReply::refreshReplyToMedia() {
|
||||
replyToDocumentId = 0;
|
||||
replyToWebPageId = 0;
|
||||
if (const auto media = replyToMsg ? replyToMsg->media() : nullptr) {
|
||||
if (const auto document = media->document()) {
|
||||
replyToDocumentId = document->id;
|
||||
} else if (const auto webpage = media->webpage()) {
|
||||
replyToWebPageId = webpage->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,7 @@ struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, Histor
|
|||
replyToMsgId = other.replyToMsgId;
|
||||
replyToMsgTop = other.replyToMsgTop;
|
||||
replyToDocumentId = other.replyToDocumentId;
|
||||
replyToWebPageId = other.replyToWebPageId;
|
||||
std::swap(replyToMsg, other.replyToMsg);
|
||||
replyToLnk = std::move(other.replyToLnk);
|
||||
replyToName = std::move(other.replyToName);
|
||||
|
@ -182,13 +183,14 @@ struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, Histor
|
|||
void setReplyToLinkFrom(
|
||||
not_null<HistoryMessage*> holder);
|
||||
|
||||
void refreshReplyToDocument();
|
||||
void refreshReplyToMedia();
|
||||
|
||||
PeerId replyToPeerId = 0;
|
||||
MsgId replyToMsgId = 0;
|
||||
MsgId replyToMsgTop = 0;
|
||||
HistoryItem *replyToMsg = nullptr;
|
||||
DocumentId replyToDocumentId = 0;
|
||||
WebPageId replyToWebPageId = 0;
|
||||
ClickHandlerPtr replyToLnk;
|
||||
mutable Ui::Text::String replyToName, replyToText;
|
||||
mutable int replyToVersion = 0;
|
||||
|
|
|
@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -1000,9 +1001,11 @@ void HistoryMessage::setCommentsItemId(FullMsgId id) {
|
|||
bool HistoryMessage::updateDependencyItem() {
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
const auto documentId = reply->replyToDocumentId;
|
||||
const auto webpageId = reply->replyToWebPageId;
|
||||
const auto result = reply->updateData(this, true);
|
||||
if (documentId != reply->replyToDocumentId
|
||||
&& generateLocalEntitiesByReply()) {
|
||||
const auto mediaIdChanged = (documentId != reply->replyToDocumentId)
|
||||
|| (webpageId != reply->replyToWebPageId);
|
||||
if (mediaIdChanged && generateLocalEntitiesByReply()) {
|
||||
reapplyText();
|
||||
}
|
||||
return result;
|
||||
|
@ -1524,34 +1527,50 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
|
|||
}
|
||||
|
||||
bool HistoryMessage::generateLocalEntitiesByReply() const {
|
||||
return !_media || _media->webpage();
|
||||
if (!_media) {
|
||||
return true;
|
||||
} else if (const auto webpage = _media->webpage()) {
|
||||
return !webpage->document && webpage->type != WebPageType::Video;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TextWithEntities HistoryMessage::withLocalEntities(
|
||||
const TextWithEntities &textWithEntities) const {
|
||||
using namespace HistoryView;
|
||||
if (!generateLocalEntitiesByReply()) {
|
||||
if (const auto webpage = _media ? _media->webpage() : nullptr) {
|
||||
if (const auto duration = DurationForTimestampLinks(webpage)) {
|
||||
return AddTimestampLinks(
|
||||
textWithEntities,
|
||||
duration,
|
||||
TimestampLinkBase(webpage, fullId()));
|
||||
}
|
||||
}
|
||||
return textWithEntities;
|
||||
}
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
const auto document = reply->replyToDocumentId
|
||||
? history()->owner().document(reply->replyToDocumentId).get()
|
||||
: nullptr;
|
||||
if (document
|
||||
&& (document->isVideoFile()
|
||||
|| document->isSong()
|
||||
|| document->isVoiceMessage())) {
|
||||
using namespace HistoryView;
|
||||
const auto duration = document->getDuration();
|
||||
const auto base = (duration > 0)
|
||||
? DocumentTimestampLinkBase(
|
||||
document,
|
||||
reply->replyToMsg->fullId())
|
||||
: QString();
|
||||
if (!base.isEmpty()) {
|
||||
const auto webpage = reply->replyToWebPageId
|
||||
? history()->owner().webpage(reply->replyToWebPageId).get()
|
||||
: nullptr;
|
||||
if (document) {
|
||||
if (const auto duration = DurationForTimestampLinks(document)) {
|
||||
const auto context = reply->replyToMsg->fullId();
|
||||
return AddTimestampLinks(
|
||||
textWithEntities,
|
||||
duration,
|
||||
base);
|
||||
TimestampLinkBase(document, context));
|
||||
}
|
||||
} else if (webpage) {
|
||||
if (const auto duration = DurationForTimestampLinks(webpage)) {
|
||||
const auto context = reply->replyToMsg->fullId();
|
||||
return AddTimestampLinks(
|
||||
textWithEntities,
|
||||
duration,
|
||||
TimestampLinkBase(webpage, context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1084,12 +1084,9 @@ TextWithEntities Document::getCaption() const {
|
|||
}
|
||||
|
||||
Ui::Text::String Document::createCaption() {
|
||||
const auto timestampLinksDuration = (_data->isSong()
|
||||
|| _data->isVoiceMessage())
|
||||
? _data->getDuration()
|
||||
: 0;
|
||||
const auto timestampLinksDuration = DurationForTimestampLinks(_data);
|
||||
const auto timestampLinkBase = timestampLinksDuration
|
||||
? DocumentTimestampLinkBase(_data, _realParent->fullId())
|
||||
? TimestampLinkBase(_data, _realParent->fullId())
|
||||
: QString();
|
||||
return File::createCaption(
|
||||
_realParent,
|
||||
|
|
|
@ -1322,11 +1322,9 @@ void Gif::refreshParentId(not_null<HistoryItem*> realParent) {
|
|||
}
|
||||
|
||||
void Gif::refreshCaption() {
|
||||
const auto timestampLinksDuration = _data->isVideoFile()
|
||||
? _data->getDuration()
|
||||
: 0;
|
||||
const auto timestampLinksDuration = DurationForTimestampLinks(_data);
|
||||
const auto timestampLinkBase = timestampLinksDuration
|
||||
? DocumentTimestampLinkBase(_data, _realParent->fullId())
|
||||
? TimestampLinkBase(_data, _realParent->fullId())
|
||||
: QString();
|
||||
_caption = createCaption(
|
||||
_parent->data(),
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lottie/lottie_single_player.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/message_bubble.h"
|
||||
|
@ -44,18 +45,77 @@ namespace {
|
|||
|
||||
} // namespace
|
||||
|
||||
QString DocumentTimestampLinkBase(
|
||||
TimeId DurationForTimestampLinks(not_null<DocumentData*> document) {
|
||||
if (!document->isVideoFile()
|
||||
&& !document->isSong()
|
||||
&& !document->isVoiceMessage()) {
|
||||
return TimeId(0);
|
||||
}
|
||||
return std::max(document->getDuration(), TimeId(0));
|
||||
}
|
||||
|
||||
QString TimestampLinkBase(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context) {
|
||||
return QString(
|
||||
"doc%1_%2_%3"
|
||||
"media_timestamp?base=doc%1_%2_%3&t="
|
||||
).arg(document->id).arg(context.channel.bare).arg(context.msg.bare);
|
||||
}
|
||||
|
||||
TimeId DurationForTimestampLinks(not_null<WebPageData*> webpage) {
|
||||
if (!webpage->collage.items.empty()) {
|
||||
return false;
|
||||
} else if (const auto document = webpage->document) {
|
||||
return DurationForTimestampLinks(document);
|
||||
} else if (webpage->type != WebPageType::Video
|
||||
|| webpage->siteName != qstr("YouTube")) {
|
||||
return TimeId(0);
|
||||
} else if (webpage->duration > 0) {
|
||||
return webpage->duration;
|
||||
}
|
||||
constexpr auto kMaxYouTubeTimestampDuration = 10 * 60 * TimeId(60);
|
||||
return kMaxYouTubeTimestampDuration;
|
||||
}
|
||||
|
||||
QString TimestampLinkBase(
|
||||
not_null<WebPageData*> webpage,
|
||||
FullMsgId context) {
|
||||
const auto url = webpage->url;
|
||||
if (url.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
auto parts = url.split(QChar('#'));
|
||||
const auto base = parts[0];
|
||||
parts.pop_front();
|
||||
const auto use = [&] {
|
||||
const auto query = base.indexOf(QChar('?'));
|
||||
if (query < 0) {
|
||||
return base + QChar('?');
|
||||
}
|
||||
auto params = base.mid(query + 1).split(QChar('&'));
|
||||
for (auto i = params.begin(); i != params.end();) {
|
||||
if (i->startsWith("t=")) {
|
||||
i = params.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return base.mid(0, query)
|
||||
+ (params.empty() ? "?" : ("?" + params.join(QChar('&')) + "&"));
|
||||
}();
|
||||
return "url:"
|
||||
+ use
|
||||
+ "t="
|
||||
+ (parts.empty() ? QString() : ("#" + parts.join(QChar('#'))));
|
||||
}
|
||||
|
||||
TextWithEntities AddTimestampLinks(
|
||||
TextWithEntities text,
|
||||
TimeId duration,
|
||||
const QString &base) {
|
||||
if (base.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
static const auto expression = QRegularExpression(
|
||||
"(?<![^\\s\\(\\)\"\\,\\.\\-])(?:(?:(\\d{1,2}):)?(\\d))?(\\d):(\\d\\d)(?![^\\s\\(\\)\",\\.\\-])");
|
||||
const auto &string = text.text;
|
||||
|
@ -104,10 +164,7 @@ TextWithEntities AddTimestampLinks(
|
|||
EntityType::CustomUrl,
|
||||
from,
|
||||
till - from,
|
||||
("internal:media_timestamp?base="
|
||||
+ base
|
||||
+ "&t="
|
||||
+ QString::number(time))));
|
||||
("internal:" + base + QString::number(time))));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
|
@ -52,9 +52,18 @@ enum class MediaInBubbleState {
|
|||
Bottom,
|
||||
};
|
||||
|
||||
[[nodiscard]] QString DocumentTimestampLinkBase(
|
||||
[[nodiscard]] TimeId DurationForTimestampLinks(
|
||||
not_null<DocumentData*> document);
|
||||
[[nodiscard]] QString TimestampLinkBase(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId context);
|
||||
|
||||
[[nodiscard]] TimeId DurationForTimestampLinks(
|
||||
not_null<WebPageData*> webpage);
|
||||
[[nodiscard]] QString TimestampLinkBase(
|
||||
not_null<WebPageData*> webpage,
|
||||
FullMsgId context);
|
||||
|
||||
[[nodiscard]] TextWithEntities AddTimestampLinks(
|
||||
TextWithEntities text,
|
||||
TimeId duration,
|
||||
|
|
|
@ -677,18 +677,16 @@ void GroupedMedia::updateNeedBubbleState() {
|
|||
QString base;
|
||||
};
|
||||
const auto timestamp = [&]() -> Timestamp {
|
||||
const auto &document = part->content->getDocument();
|
||||
if (!document || document->isAnimation()) {
|
||||
const auto document = part->content->getDocument();
|
||||
const auto duration = document
|
||||
? DurationForTimestampLinks(document)
|
||||
: TimeId(0);
|
||||
if (!duration) {
|
||||
return {};
|
||||
}
|
||||
const auto duration = document->getDuration();
|
||||
return {
|
||||
.duration = duration,
|
||||
.base = duration
|
||||
? DocumentTimestampLinkBase(
|
||||
document,
|
||||
part->item->fullId())
|
||||
: QString(),
|
||||
.base = TimestampLinkBase(document, part->item->fullId()),
|
||||
};
|
||||
}();
|
||||
_caption = createCaption(
|
||||
|
|
|
@ -2102,18 +2102,20 @@ void OverlayWidget::refreshCaption() {
|
|||
|
||||
using namespace HistoryView;
|
||||
_caption = Ui::Text::String(st::msgMinWidth);
|
||||
const auto duration = (_streamed && _document && !videoIsGifOrUserpic())
|
||||
? _document->getDuration()
|
||||
const auto duration = (_streamed && _document)
|
||||
? DurationForTimestampLinks(_document)
|
||||
: 0;
|
||||
const auto base = duration
|
||||
? DocumentTimestampLinkBase(_document, _message->fullId())
|
||||
? TimestampLinkBase(_document, _message->fullId())
|
||||
: QString();
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &_message->history()->session()
|
||||
};
|
||||
_caption.setMarkedText(
|
||||
st::mediaviewCaptionStyle,
|
||||
AddTimestampLinks(caption, duration, base),
|
||||
(base.isEmpty()
|
||||
? caption
|
||||
: AddTimestampLinks(caption, duration, base)),
|
||||
Ui::ItemTextOptions(_message),
|
||||
context);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue