mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support channel link / channel join.
This commit is contained in:
parent
f508ad5e75
commit
51d5b7bab6
12 changed files with 520 additions and 180 deletions
|
@ -35,7 +35,7 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
box-shadow: 0 0 4px -2px var(--td-history-to-down-shadow);
|
box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
@ -44,6 +44,7 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 0px;
|
||||||
}
|
}
|
||||||
.fixed_button:hover {
|
.fixed_button:hover {
|
||||||
background-color: var(--td-history-to-down-bg-over);
|
background-color: var(--td-history-to-down-bg-over);
|
||||||
|
@ -52,6 +53,8 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
fill: none;
|
fill: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
}
|
}
|
||||||
.fixed_button .ripple .inner {
|
.fixed_button .ripple .inner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -74,9 +77,10 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#top_menu svg {
|
@keyframes fadeIn {
|
||||||
width: 16px;
|
to {
|
||||||
height: 16px;
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#top_menu circle {
|
#top_menu circle {
|
||||||
fill: var(--td-history-to-down-fg);
|
fill: var(--td-history-to-down-fg);
|
||||||
|
@ -89,13 +93,21 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
#top_back path,
|
#top_back path,
|
||||||
|
#top_back line,
|
||||||
#bottom_up path {
|
#bottom_up path {
|
||||||
stroke: var(--td-history-to-down-fg);
|
stroke: var(--td-history-to-down-fg);
|
||||||
stroke-width: 2;
|
}
|
||||||
|
#top_back path,
|
||||||
|
#top_back line {
|
||||||
|
stroke-width: 1.5;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-linejoin: round;
|
stroke-linejoin: round;
|
||||||
}
|
}
|
||||||
|
#bottom_up path {
|
||||||
|
stroke-width: 1.4;
|
||||||
|
}
|
||||||
#top_back:hover path,
|
#top_back:hover path,
|
||||||
|
#top_back:hover line,
|
||||||
#bottom_up:hover path {
|
#bottom_up:hover path {
|
||||||
stroke: var(--td-history-to-down-fg-over);
|
stroke: var(--td-history-to-down-fg-over);
|
||||||
}
|
}
|
||||||
|
@ -104,9 +116,6 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
left: 10px;
|
left: 10px;
|
||||||
transition: left 200ms linear;
|
transition: left 200ms linear;
|
||||||
}
|
}
|
||||||
#top_back svg {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
#top_back.hidden {
|
#top_back.hidden {
|
||||||
left: -36px;
|
left: -36px;
|
||||||
}
|
}
|
||||||
|
@ -115,9 +124,6 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
right: 10px;
|
right: 10px;
|
||||||
transition: bottom 200ms linear;
|
transition: bottom 200ms linear;
|
||||||
}
|
}
|
||||||
#bottom_up svg {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
#bottom_up.hidden {
|
#bottom_up.hidden {
|
||||||
bottom: -36px;
|
bottom: -36px;
|
||||||
}
|
}
|
||||||
|
@ -939,16 +945,23 @@ section.channel:first-child {
|
||||||
}
|
}
|
||||||
section.channel > a {
|
section.channel > a {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 7px 18px;
|
|
||||||
background: var(--td-box-divider-bg);
|
background: var(--td-box-divider-bg);
|
||||||
}
|
}
|
||||||
section.channel > a:before {
|
section.channel > a > div.join {
|
||||||
content: var(--td-lng-group-call-join);
|
|
||||||
color: var(--td-window-active-text-fg);
|
color: var(--td-window-active-text-fg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-left: 7px;
|
padding: 7px 18px;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
section.channel.joined > a > div.join {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
section.channel > a > div.join:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
section.channel > a > div.join span:before {
|
||||||
|
content: var(--td-lng-group-call-join);
|
||||||
|
}
|
||||||
section.channel > a > h4 {
|
section.channel > a > h4 {
|
||||||
font-family: 'Helvetica Neue';
|
font-family: 'Helvetica Neue';
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
@ -959,6 +972,7 @@ section.channel > a > h4 {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
padding: 7px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iv-pullquote {
|
.iv-pullquote {
|
||||||
|
@ -976,3 +990,21 @@ section.channel > a > h4 {
|
||||||
.iv-photo {
|
.iv-photo {
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: var(--td-toast-bg);
|
||||||
|
color: var(--td-toast-fg);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
z-index: 9999;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeIn 200ms linear forwards;
|
||||||
|
}
|
||||||
|
.toast.hiding {
|
||||||
|
opacity: 1;
|
||||||
|
animation: fadeOut 1000ms linear forwards;
|
||||||
|
}
|
||||||
|
|
|
@ -5,22 +5,36 @@ var IV = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
frameClickHandler: function(e) {
|
frameClickHandler: function(e) {
|
||||||
var target = e.target, href;
|
var target = e.target;
|
||||||
do {
|
var context = '';
|
||||||
if (target.tagName == 'SUMMARY') return;
|
console.log('click', target);
|
||||||
if (target.tagName == 'DETAILS') return;
|
while (target) {
|
||||||
if (target.tagName == 'LABEL') return;
|
if (target.tagName == 'AUDIO' || target.tagName == 'VIDEO') {
|
||||||
if (target.tagName == 'AUDIO') return;
|
return;
|
||||||
if (target.tagName == 'A') break;
|
|
||||||
} while (target = target.parentNode);
|
|
||||||
if (target && target.hasAttribute('href')) {
|
|
||||||
var base_loc = document.createElement('A');
|
|
||||||
base_loc.href = window.currentUrl;
|
|
||||||
if (base_loc.origin != target.origin ||
|
|
||||||
base_loc.pathname != target.pathname ||
|
|
||||||
base_loc.search != target.search) {
|
|
||||||
IV.notify({ event: 'link_click', url: target.href });
|
|
||||||
}
|
}
|
||||||
|
if (context === ''
|
||||||
|
&& target.hasAttribute
|
||||||
|
&& target.hasAttribute('data-context')) {
|
||||||
|
context = String(target.getAttribute('data-context'));
|
||||||
|
}
|
||||||
|
if (target.tagName == 'A') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
target = target.parentNode;
|
||||||
|
}
|
||||||
|
if (!target || !target.hasAttribute('href')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var base_loc = document.createElement('A');
|
||||||
|
base_loc.href = window.currentUrl;
|
||||||
|
if (base_loc.origin != target.origin
|
||||||
|
|| base_loc.pathname != target.pathname
|
||||||
|
|| base_loc.search != target.search) {
|
||||||
|
IV.notify({
|
||||||
|
event: 'link_click',
|
||||||
|
url: target.href,
|
||||||
|
context: context,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
},
|
||||||
|
@ -71,6 +85,16 @@ var IV = {
|
||||||
document.getElementsByTagName('html')[0].style = styles;
|
document.getElementsByTagName('html')[0].style = styles;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleChannelJoined: function (id, joined) {
|
||||||
|
const channels = document.getElementsByClassName('channel');
|
||||||
|
const full = 'channel' + id;
|
||||||
|
for (var i = 0; i < channels.length; ++i) {
|
||||||
|
const channel = channels[i];
|
||||||
|
if (String(channel.getAttribute('data-context')) === full) {
|
||||||
|
channel.classList.toggle('joined', joined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
slideshowSlide: function(el, next) {
|
slideshowSlide: function(el, next) {
|
||||||
var dir = window.getComputedStyle(el, null).direction || 'ltr';
|
var dir = window.getComputedStyle(el, null).direction || 'ltr';
|
||||||
var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
|
var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
|
||||||
|
@ -172,6 +196,19 @@ var IV = {
|
||||||
IV.stopRipples(e.currentTarget);
|
IV.stopRipples(e.currentTarget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
IV.notify({ event: 'ready' });
|
||||||
|
},
|
||||||
|
showTooltip: function (text) {
|
||||||
|
var toast = document.createElement('div');
|
||||||
|
toast.classList.add('toast');
|
||||||
|
toast.textContent = text;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(function () {
|
||||||
|
toast.classList.add('hiding');
|
||||||
|
}, 2000);
|
||||||
|
setTimeout(function () {
|
||||||
|
document.body.removeChild(toast);
|
||||||
|
}, 3000);
|
||||||
},
|
},
|
||||||
toTop: function () {
|
toTop: function () {
|
||||||
document.getElementById('bottom_up').classList.add('hidden');
|
document.getElementById('bottom_up').classList.add('hidden');
|
||||||
|
|
|
@ -3515,6 +3515,34 @@ void Session::webpageApplyFields(
|
||||||
for (const auto &document : page->data().vdocuments().v) {
|
for (const auto &document : page->data().vdocuments().v) {
|
||||||
processDocument(document);
|
processDocument(document);
|
||||||
}
|
}
|
||||||
|
const auto process = [&](
|
||||||
|
const MTPPageBlock &block,
|
||||||
|
const auto &self) -> void {
|
||||||
|
block.match([&](const MTPDpageBlockChannel &data) {
|
||||||
|
processChat(data.vchannel());
|
||||||
|
}, [&](const MTPDpageBlockCover &data) {
|
||||||
|
self(data.vcover(), self);
|
||||||
|
}, [&](const MTPDpageBlockEmbedPost &data) {
|
||||||
|
for (const auto &block : data.vblocks().v) {
|
||||||
|
self(block, self);
|
||||||
|
}
|
||||||
|
}, [&](const MTPDpageBlockCollage &data) {
|
||||||
|
for (const auto &block : data.vitems().v) {
|
||||||
|
self(block, self);
|
||||||
|
}
|
||||||
|
}, [&](const MTPDpageBlockSlideshow &data) {
|
||||||
|
for (const auto &block : data.vitems().v) {
|
||||||
|
self(block, self);
|
||||||
|
}
|
||||||
|
}, [&](const MTPDpageBlockDetails &data) {
|
||||||
|
for (const auto &block : data.vblocks().v) {
|
||||||
|
self(block, self);
|
||||||
|
}
|
||||||
|
}, [](const auto &) {});
|
||||||
|
};
|
||||||
|
for (const auto &block : page->data().vblocks().v) {
|
||||||
|
process(block, process);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
webpageApplyFields(
|
webpageApplyFields(
|
||||||
page,
|
page,
|
||||||
|
|
|
@ -86,17 +86,47 @@ constexpr auto kMaxOriginalEntryLines = 8192;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] ClickHandlerPtr IvClickHandler(not_null<WebPageData*> webpage) {
|
[[nodiscard]] QString ExtractHash(
|
||||||
|
not_null<WebPageData*> webpage,
|
||||||
|
const TextWithEntities &text) {
|
||||||
|
const auto simplify = [](const QString &url) {
|
||||||
|
auto result = url.split('#')[0].toLower();
|
||||||
|
if (result.endsWith('/')) {
|
||||||
|
result.chop(1);
|
||||||
|
}
|
||||||
|
const auto prefixes = { u"http://"_q, u"https://"_q };
|
||||||
|
for (const auto &prefix : prefixes) {
|
||||||
|
if (result.startsWith(prefix)) {
|
||||||
|
result = result.mid(prefix.size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const auto simplified = simplify(webpage->url);
|
||||||
|
for (const auto &entity : text.entities) {
|
||||||
|
const auto link = (entity.type() == EntityType::Url)
|
||||||
|
? text.text.mid(entity.offset(), entity.length())
|
||||||
|
: (entity.type() == EntityType::CustomUrl)
|
||||||
|
? entity.data()
|
||||||
|
: QString();
|
||||||
|
if (simplify(link) == simplified) {
|
||||||
|
const auto i = link.indexOf('#');
|
||||||
|
return (i > 0) ? link.mid(i + 1) : QString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ClickHandlerPtr IvClickHandler(
|
||||||
|
not_null<WebPageData*> webpage,
|
||||||
|
const TextWithEntities &text) {
|
||||||
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
if (const auto iv = webpage->iv.get()) {
|
if (const auto iv = webpage->iv.get()) {
|
||||||
#ifdef _DEBUG
|
const auto hash = ExtractHash(webpage, text);
|
||||||
const auto local = base::IsCtrlPressed();
|
Core::App().iv().show(controller->uiShow(), iv, hash);
|
||||||
#else // _DEBUG
|
|
||||||
const auto local = false;
|
|
||||||
#endif // _DEBUG
|
|
||||||
Core::App().iv().show(controller->uiShow(), iv, local);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
HiddenUrlClickHandler::Open(webpage->url, context.other);
|
HiddenUrlClickHandler::Open(webpage->url, context.other);
|
||||||
|
@ -235,6 +265,7 @@ QSize WebPage::countOptimalSize() {
|
||||||
const auto lineHeight = UnitedLineHeight();
|
const auto lineHeight = UnitedLineHeight();
|
||||||
|
|
||||||
if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) {
|
if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) {
|
||||||
|
const auto original = _parent->data()->originalText();
|
||||||
const auto previewOfHiddenUrl = [&] {
|
const auto previewOfHiddenUrl = [&] {
|
||||||
if (_data->type == WebPageType::BotApp) {
|
if (_data->type == WebPageType::BotApp) {
|
||||||
// Bot Web Apps always show confirmation on hidden urls.
|
// Bot Web Apps always show confirmation on hidden urls.
|
||||||
|
@ -258,12 +289,11 @@ QSize WebPage::countOptimalSize() {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const auto simplified = simplify(_data->url);
|
const auto simplified = simplify(_data->url);
|
||||||
const auto full = _parent->data()->originalText();
|
for (const auto &entity : original.entities) {
|
||||||
for (const auto &entity : full.entities) {
|
|
||||||
if (entity.type() != EntityType::Url) {
|
if (entity.type() != EntityType::Url) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto link = full.text.mid(
|
const auto link = original.text.mid(
|
||||||
entity.offset(),
|
entity.offset(),
|
||||||
entity.length());
|
entity.length());
|
||||||
if (simplify(link) == simplified) {
|
if (simplify(link) == simplified) {
|
||||||
|
@ -272,8 +302,10 @@ QSize WebPage::countOptimalSize() {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}();
|
}();
|
||||||
_openl = _data->iv ? IvClickHandler(_data) : (previewOfHiddenUrl
|
_openl = _data->iv
|
||||||
|| UrlClickHandler::IsSuspicious(_data->url))
|
? IvClickHandler(_data, original)
|
||||||
|
: (previewOfHiddenUrl || UrlClickHandler::IsSuspicious(
|
||||||
|
_data->url))
|
||||||
? std::make_shared<HiddenUrlClickHandler>(_data->url)
|
? std::make_shared<HiddenUrlClickHandler>(_data->url)
|
||||||
: std::make_shared<UrlClickHandler>(_data->url, true);
|
: std::make_shared<UrlClickHandler>(_data->url, true);
|
||||||
if (_data->document
|
if (_data->document
|
||||||
|
|
|
@ -58,6 +58,8 @@ namespace {
|
||||||
{ "history-to-down-bg-over", &st::historyToDownBgOver },
|
{ "history-to-down-bg-over", &st::historyToDownBgOver },
|
||||||
{ "history-to-down-bg-ripple", &st::historyToDownBgRipple },
|
{ "history-to-down-bg-ripple", &st::historyToDownBgRipple },
|
||||||
{ "history-to-down-shadow", &st::historyToDownShadow },
|
{ "history-to-down-shadow", &st::historyToDownShadow },
|
||||||
|
{ "toast-bg", &st::toastBg },
|
||||||
|
{ "toast-fg", &st::toastFg },
|
||||||
};
|
};
|
||||||
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
|
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
|
||||||
{ "group-call-join", tr::lng_group_call_join },
|
{ "group-call-join", tr::lng_group_call_join },
|
||||||
|
@ -125,30 +127,125 @@ namespace {
|
||||||
.replace('\'', "\\\'");
|
.replace('\'', "\\\'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray WrapPage(
|
||||||
|
const Prepared &page,
|
||||||
|
const QByteArray &initScript) {
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
const auto classAttribute = ""_q;
|
||||||
|
#else // Q_OS_MAC
|
||||||
|
const auto classAttribute = " class=\"custom_scroll\""_q;
|
||||||
|
#endif // Q_OS_MAC
|
||||||
|
|
||||||
|
const auto js = QByteArray()
|
||||||
|
+ (page.hasCode ? "IV.initPreBlocks();" : "")
|
||||||
|
+ (page.hasEmbeds ? "IV.initEmbedBlocks();" : "")
|
||||||
|
+ "IV.init();"
|
||||||
|
+ initScript;
|
||||||
|
|
||||||
|
const auto contentAttributes = page.rtl
|
||||||
|
? " dir=\"rtl\" class=\"rtl\""_q
|
||||||
|
: QByteArray();
|
||||||
|
|
||||||
|
return R"(<!DOCTYPE html>
|
||||||
|
<html)"_q
|
||||||
|
+ classAttribute
|
||||||
|
+ R"("" style=")"
|
||||||
|
+ EscapeForAttribute(ComputeStyles())
|
||||||
|
+ R"(">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script src="/iv/page.js"></script>
|
||||||
|
<script src="/iv/highlight.js"></script>
|
||||||
|
<link rel="stylesheet" href="/iv/page.css" />
|
||||||
|
<link rel="stylesheet" href="/iv/highlight.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button class="fixed_button hidden" id="top_back" onclick="IV.back();">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<line x1="5.37464142" y1="12" x2="18.5" y2="12"></line>
|
||||||
|
<path d="M11.5,18.3 L5.27277119,12.0707223 C5.23375754,12.0316493 5.23375754,11.9683507 5.27277119,11.9292777 L11.5,5.7 L11.5,5.7"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="fixed_button" id="top_menu" onclick="IV.menu();">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="12" cy="17.4" r="1.7"></circle>
|
||||||
|
<circle cx="12" cy="12" r="1.7"></circle>
|
||||||
|
<circle cx="12" cy="6.6" r="1.7"></circle>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="fixed_button hidden" id="bottom_up" onclick="IV.toTop();">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6" transform="translate(11.997236, 12.000000) scale(-1, -1) rotate(-90.000000) translate(-11.997236, -12.000000) "></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<article)"_q + contentAttributes + ">"_q + page.content + R"(</article>
|
||||||
|
<script>)"_q + js + R"(</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)"_q;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Controller::Controller()
|
Controller::Controller()
|
||||||
: _updateStyles([=] {
|
: _updateStyles([=] {
|
||||||
const auto str = EscapeForScriptString(ComputeStyles());
|
const auto str = EscapeForScriptString(ComputeStyles());
|
||||||
if (_webview) {
|
if (_webview) {
|
||||||
_webview->eval("IV.updateStyles(\"" + str + "\");");
|
_webview->eval("IV.updateStyles('" + str + "');");
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::~Controller() {
|
Controller::~Controller() {
|
||||||
|
_ready = false;
|
||||||
_webview = nullptr;
|
_webview = nullptr;
|
||||||
_title = nullptr;
|
_title = nullptr;
|
||||||
_window = nullptr;
|
_window = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::show(const QString &dataPath, Prepared page) {
|
void Controller::show(
|
||||||
|
const QString &dataPath,
|
||||||
|
Prepared page,
|
||||||
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||||
createWindow();
|
createWindow();
|
||||||
|
const auto js = fillInChannelValuesScript(std::move(inChannelValues));
|
||||||
|
|
||||||
_titleText.setText(st::ivTitle.style, page.title);
|
_titleText.setText(st::ivTitle.style, page.title);
|
||||||
InvokeQueued(_container, [=, page = std::move(page)]() mutable {
|
InvokeQueued(_container, [=, page = std::move(page)]() mutable {
|
||||||
showInWindow(dataPath, std::move(page));
|
showInWindow(dataPath, std::move(page), js);
|
||||||
|
if (!_webview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray Controller::fillInChannelValuesScript(
|
||||||
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||||
|
auto result = QByteArray();
|
||||||
|
for (auto &[id, in] : inChannelValues) {
|
||||||
|
std::move(in) | rpl::start_with_next([=](bool in) {
|
||||||
|
if (_ready) {
|
||||||
|
_webview->eval(toggleInChannelScript(id, in));
|
||||||
|
} else {
|
||||||
|
_inChannelChanged[id] = in;
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
for (const auto &[id, in] : base::take(_inChannelChanged)) {
|
||||||
|
result += toggleInChannelScript(id, in);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Controller::toggleInChannelScript(
|
||||||
|
const QByteArray &id,
|
||||||
|
bool in) const {
|
||||||
|
const auto value = in ? "true" : "false";
|
||||||
|
return "IV.toggleChannelJoined('" + id + "', " + value + ");";
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::updateTitleGeometry() {
|
void Controller::updateTitleGeometry() {
|
||||||
_title->setGeometry(0, 0, _window->width(), st::ivTitle.height);
|
_title->setGeometry(0, 0, _window->width(), st::ivTitle.height);
|
||||||
}
|
}
|
||||||
|
@ -242,7 +339,10 @@ void Controller::createWindow() {
|
||||||
window->show();
|
window->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
void Controller::showInWindow(
|
||||||
|
const QString &dataPath,
|
||||||
|
Prepared page,
|
||||||
|
const QByteArray &initScript) {
|
||||||
Expects(_container != nullptr);
|
Expects(_container != nullptr);
|
||||||
|
|
||||||
const auto window = _window.get();
|
const auto window = _window.get();
|
||||||
|
@ -255,10 +355,11 @@ void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
||||||
const auto raw = _webview.get();
|
const auto raw = _webview.get();
|
||||||
|
|
||||||
window->lifetime().add([=] {
|
window->lifetime().add([=] {
|
||||||
|
_ready = false;
|
||||||
_webview = nullptr;
|
_webview = nullptr;
|
||||||
});
|
});
|
||||||
if (!raw->widget()) {
|
if (!raw->widget()) {
|
||||||
_events.fire(Event::Close);
|
_events.fire({ Event::Type::Close });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window->events(
|
window->events(
|
||||||
|
@ -291,20 +392,24 @@ void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
||||||
if (event == u"keydown"_q) {
|
if (event == u"keydown"_q) {
|
||||||
const auto key = object.value("key").toString();
|
const auto key = object.value("key").toString();
|
||||||
const auto modifier = object.value("modifier").toString();
|
const auto modifier = object.value("modifier").toString();
|
||||||
const auto ctrl = Platform::IsMac() ? u"cmd"_q : u"ctrl"_q;
|
processKey(key, modifier);
|
||||||
if (key == u"escape"_q) {
|
|
||||||
escape();
|
|
||||||
} else if (key == u"w"_q && modifier == ctrl) {
|
|
||||||
close();
|
|
||||||
} else if (key == u"m"_q && modifier == ctrl) {
|
|
||||||
minimize();
|
|
||||||
} else if (key == u"q"_q && modifier == ctrl) {
|
|
||||||
quit();
|
|
||||||
}
|
|
||||||
} else if (event == u"mouseenter"_q) {
|
} else if (event == u"mouseenter"_q) {
|
||||||
window->overrideSystemButtonOver({});
|
window->overrideSystemButtonOver({});
|
||||||
} else if (event == u"mouseup"_q) {
|
} else if (event == u"mouseup"_q) {
|
||||||
window->overrideSystemButtonDown({});
|
window->overrideSystemButtonDown({});
|
||||||
|
} else if (event == u"link_click"_q) {
|
||||||
|
const auto url = object.value("url").toString();
|
||||||
|
const auto context = object.value("context").toString();
|
||||||
|
processLink(url, context);
|
||||||
|
} else if (event == u"ready"_q) {
|
||||||
|
_ready = true;
|
||||||
|
auto script = QByteArray();
|
||||||
|
for (const auto &[id, in] : base::take(_inChannelChanged)) {
|
||||||
|
script += toggleInChannelScript(id, in);
|
||||||
|
}
|
||||||
|
if (!script.isEmpty()) {
|
||||||
|
_webview->eval(script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -323,11 +428,6 @@ void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
||||||
};
|
};
|
||||||
const auto id = std::string_view(request.id).substr(3);
|
const auto id = std::string_view(request.id).substr(3);
|
||||||
if (id == "page.html") {
|
if (id == "page.html") {
|
||||||
const auto i = page.html.indexOf("<html"_q);
|
|
||||||
Assert(i >= 0);
|
|
||||||
const auto colored = page.html.mid(0, i + 5)
|
|
||||||
+ " style=\"" + EscapeForAttribute(ComputeStyles()) + "\""
|
|
||||||
+ page.html.mid(i + 5);
|
|
||||||
if (!_subscribedToColors) {
|
if (!_subscribedToColors) {
|
||||||
_subscribedToColors = true;
|
_subscribedToColors = true;
|
||||||
|
|
||||||
|
@ -338,7 +438,7 @@ void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
||||||
_updateStyles.call();
|
_updateStyles.call();
|
||||||
}, _webview->lifetime());
|
}, _webview->lifetime());
|
||||||
}
|
}
|
||||||
return finishWith(colored, "text/html");
|
return finishWith(WrapPage(page, initScript), "text/html");
|
||||||
}
|
}
|
||||||
const auto css = id.ends_with(".css");
|
const auto css = id.ends_with(".css");
|
||||||
const auto js = !css && id.ends_with(".js");
|
const auto js = !css && id.ends_with(".js");
|
||||||
|
@ -357,15 +457,52 @@ void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
||||||
return Webview::DataResult::Failed;
|
return Webview::DataResult::Failed;
|
||||||
});
|
});
|
||||||
|
|
||||||
raw->init(R"(
|
raw->init(R"()");
|
||||||
)");
|
|
||||||
raw->navigateToData("iv/page.html");
|
raw->navigateToData("iv/page.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::processKey(const QString &key, const QString &modifier) {
|
||||||
|
const auto ctrl = Platform::IsMac() ? u"cmd"_q : u"ctrl"_q;
|
||||||
|
if (key == u"escape"_q) {
|
||||||
|
escape();
|
||||||
|
} else if (key == u"w"_q && modifier == ctrl) {
|
||||||
|
close();
|
||||||
|
} else if (key == u"m"_q && modifier == ctrl) {
|
||||||
|
minimize();
|
||||||
|
} else if (key == u"q"_q && modifier == ctrl) {
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::processLink(const QString &url, const QString &context) {
|
||||||
|
const auto channelPrefix = u"channel"_q;
|
||||||
|
const auto joinPrefix = u"join_link"_q;
|
||||||
|
if (context.startsWith(channelPrefix)) {
|
||||||
|
_events.fire({
|
||||||
|
Event::Type::OpenChannel,
|
||||||
|
context.mid(channelPrefix.size()),
|
||||||
|
});
|
||||||
|
} else if (context.startsWith(joinPrefix)) {
|
||||||
|
_events.fire({
|
||||||
|
Event::Type::JoinChannel,
|
||||||
|
context.mid(joinPrefix.size()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Controller::active() const {
|
bool Controller::active() const {
|
||||||
return _window && _window->isActiveWindow();
|
return _window && _window->isActiveWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::showJoinedTooltip() {
|
||||||
|
if (_webview) {
|
||||||
|
_webview->eval("IV.showTooltip('"
|
||||||
|
+ EscapeForScriptString(
|
||||||
|
tr::lng_action_you_joined(tr::now).toUtf8())
|
||||||
|
+ "');");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::minimize() {
|
void Controller::minimize() {
|
||||||
if (_window) {
|
if (_window) {
|
||||||
_window->setWindowState(_window->windowState()
|
_window->setWindowState(_window->windowState()
|
||||||
|
@ -378,11 +515,11 @@ void Controller::escape() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::close() {
|
void Controller::close() {
|
||||||
_events.fire(Event::Close);
|
_events.fire({ Event::Type::Close });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::quit() {
|
void Controller::quit() {
|
||||||
_events.fire(Event::Quit);
|
_events.fire({ Event::Type::Quit });
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::lifetime &Controller::lifetime() {
|
rpl::lifetime &Controller::lifetime() {
|
||||||
|
|
|
@ -32,13 +32,23 @@ public:
|
||||||
Controller();
|
Controller();
|
||||||
~Controller();
|
~Controller();
|
||||||
|
|
||||||
enum class Event {
|
struct Event {
|
||||||
Close,
|
enum class Type {
|
||||||
Quit,
|
Close,
|
||||||
|
Quit,
|
||||||
|
OpenChannel,
|
||||||
|
JoinChannel,
|
||||||
|
};
|
||||||
|
Type type = Type::Close;
|
||||||
|
QString context;
|
||||||
};
|
};
|
||||||
|
|
||||||
void show(const QString &dataPath, Prepared page);
|
void show(
|
||||||
|
const QString &dataPath,
|
||||||
|
Prepared page,
|
||||||
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
||||||
[[nodiscard]] bool active() const;
|
[[nodiscard]] bool active() const;
|
||||||
|
void showJoinedTooltip();
|
||||||
void minimize();
|
void minimize();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<Webview::DataRequest> dataRequests() const {
|
[[nodiscard]] rpl::producer<Webview::DataRequest> dataRequests() const {
|
||||||
|
@ -55,7 +65,18 @@ private:
|
||||||
void createWindow();
|
void createWindow();
|
||||||
void updateTitleGeometry();
|
void updateTitleGeometry();
|
||||||
void paintTitle(Painter &p, QRect clip);
|
void paintTitle(Painter &p, QRect clip);
|
||||||
void showInWindow(const QString &dataPath, Prepared page);
|
void showInWindow(
|
||||||
|
const QString &dataPath,
|
||||||
|
Prepared page,
|
||||||
|
const QByteArray &initScript);
|
||||||
|
[[nodiscard]] QByteArray fillInChannelValuesScript(
|
||||||
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
||||||
|
[[nodiscard]] QByteArray toggleInChannelScript(
|
||||||
|
const QByteArray &id,
|
||||||
|
bool in) const;
|
||||||
|
|
||||||
|
void processKey(const QString &key, const QString &modifier);
|
||||||
|
void processLink(const QString &url, const QString &context);
|
||||||
|
|
||||||
void escape();
|
void escape();
|
||||||
void close();
|
void close();
|
||||||
|
@ -70,8 +91,10 @@ private:
|
||||||
std::unique_ptr<Webview::Window> _webview;
|
std::unique_ptr<Webview::Window> _webview;
|
||||||
rpl::event_stream<Webview::DataRequest> _dataRequests;
|
rpl::event_stream<Webview::DataRequest> _dataRequests;
|
||||||
rpl::event_stream<Event> _events;
|
rpl::event_stream<Event> _events;
|
||||||
|
base::flat_map<QByteArray, bool> _inChannelChanged;
|
||||||
SingleQueuedInvokation _updateStyles;
|
SingleQueuedInvokation _updateStyles;
|
||||||
bool _subscribedToColors = false;
|
bool _subscribedToColors = false;
|
||||||
|
bool _ready = false;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,13 @@ struct Options {
|
||||||
|
|
||||||
struct Prepared {
|
struct Prepared {
|
||||||
QString title;
|
QString title;
|
||||||
QByteArray html;
|
QByteArray content;
|
||||||
std::vector<QByteArray> resources;
|
std::vector<QByteArray> resources;
|
||||||
base::flat_map<QByteArray, QByteArray> embeds;
|
base::flat_map<QByteArray, QByteArray> embeds;
|
||||||
|
base::flat_set<QByteArray> channelIds;
|
||||||
|
bool rtl = false;
|
||||||
|
bool hasCode = false;
|
||||||
|
bool hasEmbeds = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Geo {
|
struct Geo {
|
||||||
|
|
|
@ -7,13 +7,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "iv/iv_instance.h"
|
#include "iv/iv_instance.h"
|
||||||
|
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "core/application.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "core/shortcuts.h"
|
#include "core/shortcuts.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
#include "data/data_cloud_file.h"
|
#include "data/data_cloud_file.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_photo_media.h"
|
#include "data/data_photo_media.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "info/profile/info_profile_values.h"
|
||||||
#include "iv/iv_controller.h"
|
#include "iv/iv_controller.h"
|
||||||
#include "iv/iv_data.h"
|
#include "iv/iv_data.h"
|
||||||
#include "main/main_account.h"
|
#include "main/main_account.h"
|
||||||
|
@ -26,6 +31,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "webview/webview_data_stream_memory.h"
|
#include "webview/webview_data_stream_memory.h"
|
||||||
#include "webview/webview_interface.h"
|
#include "webview/webview_interface.h"
|
||||||
|
#include "window/window_controller.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "window/window_session_controller_link_info.h"
|
||||||
|
|
||||||
namespace Iv {
|
namespace Iv {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -66,6 +74,7 @@ public:
|
||||||
[[nodiscard]] bool activeFor(not_null<Main::Session*> session) const;
|
[[nodiscard]] bool activeFor(not_null<Main::Session*> session) const;
|
||||||
[[nodiscard]] bool active() const;
|
[[nodiscard]] bool active() const;
|
||||||
|
|
||||||
|
void showJoinedTooltip();
|
||||||
void minimize();
|
void minimize();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<Controller::Event> events() const {
|
[[nodiscard]] rpl::producer<Controller::Event> events() const {
|
||||||
|
@ -123,6 +132,7 @@ private:
|
||||||
void streamMap(QString params, Webview::DataRequest request);
|
void streamMap(QString params, Webview::DataRequest request);
|
||||||
void sendEmbed(QByteArray hash, Webview::DataRequest request);
|
void sendEmbed(QByteArray hash, Webview::DataRequest request);
|
||||||
|
|
||||||
|
void fillChannelJoinedValues(const Prepared &result);
|
||||||
void requestDone(
|
void requestDone(
|
||||||
Webview::DataRequest request,
|
Webview::DataRequest request,
|
||||||
QByteArray bytes,
|
QByteArray bytes,
|
||||||
|
@ -136,6 +146,7 @@ private:
|
||||||
QString _id;
|
QString _id;
|
||||||
std::unique_ptr<Controller> _controller;
|
std::unique_ptr<Controller> _controller;
|
||||||
base::flat_map<DocumentId, FileLoad> _files;
|
base::flat_map<DocumentId, FileLoad> _files;
|
||||||
|
base::flat_map<QByteArray, rpl::producer<bool>> _inChannelValues;
|
||||||
|
|
||||||
QString _localBase;
|
QString _localBase;
|
||||||
base::flat_map<QByteArray, QByteArray> _embeds;
|
base::flat_map<QByteArray, QByteArray> _embeds;
|
||||||
|
@ -162,6 +173,7 @@ Shown::Shown(
|
||||||
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
|
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
|
||||||
crl::on_main(weak, [=, result = std::move(result)]() mutable {
|
crl::on_main(weak, [=, result = std::move(result)]() mutable {
|
||||||
_embeds = std::move(result.embeds);
|
_embeds = std::move(result.embeds);
|
||||||
|
fillChannelJoinedValues(result);
|
||||||
if (!base.isEmpty()) {
|
if (!base.isEmpty()) {
|
||||||
_localBase = base;
|
_localBase = base;
|
||||||
showLocal(std::move(result));
|
showLocal(std::move(result));
|
||||||
|
@ -172,6 +184,22 @@ Shown::Shown(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shown::fillChannelJoinedValues(const Prepared &result) {
|
||||||
|
for (const auto &id : result.channelIds) {
|
||||||
|
const auto channelId = ChannelId(id.toLongLong());
|
||||||
|
const auto channel = _session->data().channel(channelId);
|
||||||
|
if (!channel->isLoaded() && !channel->username().isEmpty()) {
|
||||||
|
channel->session().api().request(MTPcontacts_ResolveUsername(
|
||||||
|
MTP_string(channel->username())
|
||||||
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
|
channel->owner().processUsers(result.data().vusers());
|
||||||
|
channel->owner().processChats(result.data().vchats());
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
_inChannelValues[id] = Info::Profile::AmInChannelValue(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Shown::showLocal(Prepared result) {
|
void Shown::showLocal(Prepared result) {
|
||||||
showProgress(0);
|
showProgress(0);
|
||||||
|
|
||||||
|
@ -179,7 +207,7 @@ void Shown::showLocal(Prepared result) {
|
||||||
QDir().mkpath(_localBase);
|
QDir().mkpath(_localBase);
|
||||||
|
|
||||||
_resources = std::move(result.resources);
|
_resources = std::move(result.resources);
|
||||||
writeLocal(localRoot(), result.html);
|
writeLocal(localRoot(), result.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shown::showProgress(int index) {
|
void Shown::showProgress(int index) {
|
||||||
|
@ -392,7 +420,10 @@ void Shown::showWindowed(Prepared result) {
|
||||||
}, _controller->lifetime());
|
}, _controller->lifetime());
|
||||||
|
|
||||||
const auto domain = &_session->domain();
|
const auto domain = &_session->domain();
|
||||||
_controller->show(domain->local().webviewDataPath(), std::move(result));
|
_controller->show(
|
||||||
|
domain->local().webviewDataPath(),
|
||||||
|
std::move(result),
|
||||||
|
base::duplicate(_inChannelValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shown::streamPhoto(PhotoId photoId, Webview::DataRequest request) {
|
void Shown::streamPhoto(PhotoId photoId, Webview::DataRequest request) {
|
||||||
|
@ -651,6 +682,12 @@ bool Shown::active() const {
|
||||||
return _controller && _controller->active();
|
return _controller && _controller->active();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shown::showJoinedTooltip() {
|
||||||
|
if (_controller) {
|
||||||
|
_controller->showJoinedTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Shown::minimize() {
|
void Shown::minimize() {
|
||||||
if (_controller) {
|
if (_controller) {
|
||||||
_controller->minimize();
|
_controller->minimize();
|
||||||
|
@ -670,11 +707,34 @@ void Instance::show(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_shown = std::make_unique<Shown>(show, data, local);
|
_shown = std::make_unique<Shown>(show, data, local);
|
||||||
|
_shownSession = session;
|
||||||
_shown->events() | rpl::start_with_next([=](Controller::Event event) {
|
_shown->events() | rpl::start_with_next([=](Controller::Event event) {
|
||||||
if (event == Controller::Event::Close) {
|
using Type = Controller::Event::Type;
|
||||||
|
switch (event.type) {
|
||||||
|
case Type::Close:
|
||||||
_shown = nullptr;
|
_shown = nullptr;
|
||||||
} else if (event == Controller::Event::Quit) {
|
break;
|
||||||
|
case Type::Quit:
|
||||||
Shortcuts::Launch(Shortcuts::Command::Quit);
|
Shortcuts::Launch(Shortcuts::Command::Quit);
|
||||||
|
break;
|
||||||
|
case Type::OpenChannel:
|
||||||
|
processOpenChannel(event.context);
|
||||||
|
break;
|
||||||
|
case Type::JoinChannel:
|
||||||
|
processJoinChannel(event.context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, _shown->lifetime());
|
||||||
|
|
||||||
|
session->changes().peerUpdates(
|
||||||
|
::Data::PeerUpdate::Flag::ChannelAmIn
|
||||||
|
) | rpl::start_with_next([=](const ::Data::PeerUpdate &update) {
|
||||||
|
if (const auto channel = update.peer->asChannel()) {
|
||||||
|
if (channel->amIn()) {
|
||||||
|
if (_joining.remove(not_null(channel))) {
|
||||||
|
_shown->showJoinedTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, _shown->lifetime());
|
}, _shown->lifetime());
|
||||||
|
|
||||||
|
@ -682,6 +742,16 @@ void Instance::show(
|
||||||
_tracking.emplace(session);
|
_tracking.emplace(session);
|
||||||
session->lifetime().add([=] {
|
session->lifetime().add([=] {
|
||||||
_tracking.remove(session);
|
_tracking.remove(session);
|
||||||
|
for (auto i = begin(_joining); i != end(_joining);) {
|
||||||
|
if (&(*i)->session() == session) {
|
||||||
|
i = _joining.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_shownSession == session) {
|
||||||
|
_shownSession = nullptr;
|
||||||
|
}
|
||||||
if (_shown && _shown->showingFrom(session)) {
|
if (_shown && _shown->showingFrom(session)) {
|
||||||
_shown = nullptr;
|
_shown = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -689,6 +759,52 @@ void Instance::show(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Instance::processOpenChannel(const QString &context) {
|
||||||
|
if (!_shownSession) {
|
||||||
|
return;
|
||||||
|
} else if (const auto channelId = ChannelId(context.toLongLong())) {
|
||||||
|
const auto channel = _shownSession->data().channel(channelId);
|
||||||
|
if (channel->isLoaded()) {
|
||||||
|
if (const auto window = Core::App().windowFor(channel)) {
|
||||||
|
if (const auto controller = window->sessionController()) {
|
||||||
|
controller->showPeerHistory(channel);
|
||||||
|
_shown = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!channel->username().isEmpty()) {
|
||||||
|
if (const auto window = Core::App().windowFor(channel)) {
|
||||||
|
if (const auto controller = window->sessionController()) {
|
||||||
|
controller->showPeerByLink({
|
||||||
|
.usernameOrId = channel->username(),
|
||||||
|
});
|
||||||
|
_shown = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::processJoinChannel(const QString &context) {
|
||||||
|
if (!_shownSession) {
|
||||||
|
return;
|
||||||
|
} else if (const auto channelId = ChannelId(context.toLongLong())) {
|
||||||
|
const auto channel = _shownSession->data().channel(channelId);
|
||||||
|
_joining.emplace(channel);
|
||||||
|
if (channel->isLoaded()) {
|
||||||
|
_shownSession->api().joinChannel(channel);
|
||||||
|
} else if (!channel->username().isEmpty()) {
|
||||||
|
if (const auto window = Core::App().windowFor(channel)) {
|
||||||
|
if (const auto controller = window->sessionController()) {
|
||||||
|
controller->showPeerByLink({
|
||||||
|
.usernameOrId = channel->username(),
|
||||||
|
.joinChannel = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Instance::hasActiveWindow(not_null<Main::Session*> session) const {
|
bool Instance::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||||
return _shown && _shown->activeFor(session);
|
return _shown && _shown->activeFor(session);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,13 @@ public:
|
||||||
[[nodiscard]] rpl::lifetime &lifetime();
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void processOpenChannel(const QString &context);
|
||||||
|
void processJoinChannel(const QString &context);
|
||||||
|
|
||||||
std::unique_ptr<Shown> _shown;
|
std::unique_ptr<Shown> _shown;
|
||||||
|
Main::Session *_shownSession = nullptr;
|
||||||
base::flat_set<not_null<Main::Session*>> _tracking;
|
base::flat_set<not_null<Main::Session*>> _tracking;
|
||||||
|
base::flat_set<not_null<ChannelData*>> _joining;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
|
|
@ -47,14 +47,6 @@ private:
|
||||||
void process(const MTPPhoto &photo);
|
void process(const MTPPhoto &photo);
|
||||||
void process(const MTPDocument &document);
|
void process(const MTPDocument &document);
|
||||||
|
|
||||||
[[nodiscard]] QByteArray prepare(QByteArray body);
|
|
||||||
|
|
||||||
[[nodiscard]] QByteArray html(
|
|
||||||
const QByteArray &head,
|
|
||||||
const QByteArray &body);
|
|
||||||
|
|
||||||
[[nodiscard]] QByteArray page(const MTPDpage &data);
|
|
||||||
|
|
||||||
template <typename Inner>
|
template <typename Inner>
|
||||||
[[nodiscard]] QByteArray list(const MTPVector<Inner> &data);
|
[[nodiscard]] QByteArray list(const MTPVector<Inner> &data);
|
||||||
|
|
||||||
|
@ -143,9 +135,6 @@ private:
|
||||||
base::flat_map<uint64, Photo> _photosById;
|
base::flat_map<uint64, Photo> _photosById;
|
||||||
base::flat_map<uint64, Document> _documentsById;
|
base::flat_map<uint64, Document> _documentsById;
|
||||||
|
|
||||||
bool _hasCode = false;
|
|
||||||
bool _hasEmbeds = false;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] bool IsVoidElement(const QByteArray &name) {
|
[[nodiscard]] bool IsVoidElement(const QByteArray &name) {
|
||||||
|
@ -169,11 +158,11 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Parser(const Source &source, const Options &options)
|
Parser::Parser(const Source &source, const Options &options)
|
||||||
: _options(options)
|
: _options(options) {
|
||||||
, _rtl(source.page.data().is_rtl()) {
|
|
||||||
process(source);
|
process(source);
|
||||||
_result.title = source.title;
|
_result.title = source.title;
|
||||||
_result.html = prepare(page(source.page.data()));
|
_result.rtl = source.page.data().is_rtl();
|
||||||
|
_result.content = list(source.page.data().vblocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
Prepared Parser::result() {
|
Prepared Parser::result() {
|
||||||
|
@ -260,7 +249,7 @@ QByteArray Parser::block(const MTPDpageBlockPreformatted &data) {
|
||||||
if (!language.isEmpty()) {
|
if (!language.isEmpty()) {
|
||||||
list.push_back({ "data-language", language });
|
list.push_back({ "data-language", language });
|
||||||
list.push_back({ "class", "lang-" + language });
|
list.push_back({ "class", "lang-" + language });
|
||||||
_hasCode = true;
|
_result.hasCode = true;
|
||||||
}
|
}
|
||||||
return tag("pre", list, rich(data.vtext()));
|
return tag("pre", list, rich(data.vtext()));
|
||||||
}
|
}
|
||||||
|
@ -270,7 +259,7 @@ QByteArray Parser::block(const MTPDpageBlockFooter &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::block(const MTPDpageBlockDivider &data) {
|
QByteArray Parser::block(const MTPDpageBlockDivider &data) {
|
||||||
return tag("hr", { {"class", "iv-divider" } });
|
return tag("hr", { { "class", "iv-divider" } });
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::block(const MTPDpageBlockAnchor &data) {
|
QByteArray Parser::block(const MTPDpageBlockAnchor &data) {
|
||||||
|
@ -393,7 +382,7 @@ QByteArray Parser::block(const MTPDpageBlockCover &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::block(const MTPDpageBlockEmbed &data) {
|
QByteArray Parser::block(const MTPDpageBlockEmbed &data) {
|
||||||
_hasEmbeds = true;
|
_result.hasEmbeds = true;
|
||||||
auto eclass = data.is_full_width() ? QByteArray() : "nowide";
|
auto eclass = data.is_full_width() ? QByteArray() : "nowide";
|
||||||
auto width = QByteArray();
|
auto width = QByteArray();
|
||||||
auto height = QByteArray();
|
auto height = QByteArray();
|
||||||
|
@ -519,6 +508,9 @@ QByteArray Parser::block(const MTPDpageBlockSlideshow &data) {
|
||||||
QByteArray Parser::block(const MTPDpageBlockChannel &data) {
|
QByteArray Parser::block(const MTPDpageBlockChannel &data) {
|
||||||
auto name = QByteArray();
|
auto name = QByteArray();
|
||||||
auto username = QByteArray();
|
auto username = QByteArray();
|
||||||
|
auto id = data.vchannel().match([](const auto &data) {
|
||||||
|
return QByteArray::number(data.vid().v);
|
||||||
|
});
|
||||||
data.vchannel().match([&](const MTPDchannel &data) {
|
data.vchannel().match([&](const MTPDchannel &data) {
|
||||||
if (const auto has = data.vusername()) {
|
if (const auto has = data.vusername()) {
|
||||||
username = utf(*has);
|
username = utf(*has);
|
||||||
|
@ -528,15 +520,23 @@ QByteArray Parser::block(const MTPDpageBlockChannel &data) {
|
||||||
name = utf(data.vtitle());
|
name = utf(data.vtitle());
|
||||||
}, [](const auto &) {
|
}, [](const auto &) {
|
||||||
});
|
});
|
||||||
auto result = tag("h4", name);
|
auto result = tag(
|
||||||
if (!username.isEmpty()) {
|
"div",
|
||||||
const auto link = "https://t.me/" + username;
|
{ { "class", "join" }, { "data-context", "join_link" + id } },
|
||||||
result = tag(
|
tag("span")
|
||||||
"a",
|
) + tag("h4", name);
|
||||||
{ { "href", link }, { "target", "_blank" } },
|
const auto link = username.isEmpty()
|
||||||
result);
|
? "javascript:alert('Channel Link');"
|
||||||
}
|
: "https://t.me/" + username;
|
||||||
return tag("section", { { "class", "channel" } }, result);
|
result = tag(
|
||||||
|
"a",
|
||||||
|
{ { "href", link }, { "data-context", "channel" + id } },
|
||||||
|
result);
|
||||||
|
_result.channelIds.emplace(id);
|
||||||
|
return tag("section", {
|
||||||
|
{ "class", "channel joined" },
|
||||||
|
{ "data-context", "channel" + id },
|
||||||
|
}, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::block(const MTPDpageBlockAudio &data) {
|
QByteArray Parser::block(const MTPDpageBlockAudio &data) {
|
||||||
|
@ -972,83 +972,6 @@ QByteArray Parser::resource(QByteArray id) {
|
||||||
return toFolder ? id : ('/' + id);
|
return toFolder ? id : ('/' + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::page(const MTPDpage &data) {
|
|
||||||
const auto html = list(data.vblocks());
|
|
||||||
if (html.isEmpty()) {
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
auto attributes = Attributes();
|
|
||||||
if (_rtl) {
|
|
||||||
attributes.push_back({ "dir", "rtl" });
|
|
||||||
attributes.push_back({ "class", "rtl" });
|
|
||||||
}
|
|
||||||
return tag("article", attributes, html);
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Parser::prepare(QByteArray body) {
|
|
||||||
auto head = QByteArray();
|
|
||||||
auto js = QByteArray();
|
|
||||||
if (body.isEmpty()) {
|
|
||||||
body = tag(
|
|
||||||
"section",
|
|
||||||
{ { "class", "message" } },
|
|
||||||
tag("aside", "Failed." + tag("cite", "Failed.")));
|
|
||||||
}
|
|
||||||
if (_hasCode) {
|
|
||||||
head += R"(
|
|
||||||
<link rel="stylesheet" href=")" + resource("iv/highlight.css") + R"(">
|
|
||||||
<script src=")" + resource("iv/highlight.js") + R"("></script>
|
|
||||||
)"_q;
|
|
||||||
js += "IV.initPreBlocks();";
|
|
||||||
}
|
|
||||||
if (_hasEmbeds) {
|
|
||||||
js += "IV.initEmbedBlocks();";
|
|
||||||
}
|
|
||||||
body += tag("script", js + "IV.init();");
|
|
||||||
return html(head, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray Parser::html(const QByteArray &head, const QByteArray &body) {
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
const auto classAttribute = ""_q;
|
|
||||||
#else // Q_OS_MAC
|
|
||||||
const auto classAttribute = " class=\"custom_scroll\""_q;
|
|
||||||
#endif // Q_OS_MAC
|
|
||||||
|
|
||||||
return R"(<!DOCTYPE html>
|
|
||||||
<html)"_q + classAttribute + R"(">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="robots" content="noindex, nofollow">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<script src=")" + resource("iv/page.js") + R"("></script>
|
|
||||||
<link rel="stylesheet" href=")" + resource("iv/page.css") + R"(" />
|
|
||||||
)"_q + head + R"(
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<button class="fixed_button hidden" id="top_back" onclick="IV.back();">
|
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M17 13L12 18L7 13M12 6L12 17"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="fixed_button" id="top_menu" onclick="IV.menu();">
|
|
||||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle cx="8" cy="2.5" r="1.6"></circle>
|
|
||||||
<circle cx="8" cy="8" r="1.6"></circle>
|
|
||||||
<circle cx="8" cy="13.5" r="1.6"></circle>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="fixed_button hidden" id="bottom_up" onclick="IV.toTop();">
|
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M17 13L12 18L7 13M12 6L12 17"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)"_q + body + R"(
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)"_q;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Prepared Prepare(const Source &source, const Options &options) {
|
Prepared Prepare(const Source &source, const Options &options) {
|
||||||
|
|
|
@ -314,6 +314,8 @@ void SessionNavigation::showPeerByLink(const PeerByLinkInfo &info) {
|
||||||
peer,
|
peer,
|
||||||
[=](bool) { showPeerByLinkResolved(peer, info); },
|
[=](bool) { showPeerByLinkResolved(peer, info); },
|
||||||
true);
|
true);
|
||||||
|
} else if (info.joinChannel && peer->isChannel()) {
|
||||||
|
peer->session().api().joinChannel(peer->asChannel());
|
||||||
} else {
|
} else {
|
||||||
showPeerByLinkResolved(peer, info);
|
showPeerByLinkResolved(peer, info);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ struct PeerByLinkInfo {
|
||||||
QString startToken;
|
QString startToken;
|
||||||
ChatAdminRights startAdminRights;
|
ChatAdminRights startAdminRights;
|
||||||
bool startAutoSubmit = false;
|
bool startAutoSubmit = false;
|
||||||
|
bool joinChannel = false;
|
||||||
QString botAppName;
|
QString botAppName;
|
||||||
bool botAppForceConfirmation = false;
|
bool botAppForceConfirmation = false;
|
||||||
QString attachBotUsername;
|
QString attachBotUsername;
|
||||||
|
|
Loading…
Add table
Reference in a new issue