mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Load full webpage and update in IV.
This commit is contained in:
parent
0a87dbea68
commit
315859bf7b
10 changed files with 199 additions and 38 deletions
1
Telegram/Resources/iv_html/morphdom-umd.min.2.7.2.js
Normal file
1
Telegram/Resources/iv_html/morphdom-umd.min.2.7.2.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -335,39 +335,82 @@ var IV = {
|
|||
}
|
||||
IV.pending = [index, hash];
|
||||
if (!IV.cache[index]) {
|
||||
IV.cache[index] = { loading: true };
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onload = function () {
|
||||
IV.cache[index].loading = false;
|
||||
IV.cache[index].content = xhr.responseText;
|
||||
if (IV.pending && IV.pending[0] == index) {
|
||||
IV.navigateToLoaded(index, IV.pending[1]);
|
||||
}
|
||||
}
|
||||
|
||||
xhr.open('GET', 'page' + index + '.json');
|
||||
xhr.send();
|
||||
IV.loadPage(index);
|
||||
} else if (IV.cache[index].dom) {
|
||||
IV.navigateToDOM(index, hash);
|
||||
} else if (IV.cache[index].content) {
|
||||
IV.navigateToLoaded(index, hash);
|
||||
}
|
||||
},
|
||||
applyUpdatedContent: function (index) {
|
||||
if (IV.index != index) {
|
||||
IV.cache[index].contentUpdated = (IV.cache[index].dom !== undefined);
|
||||
return;
|
||||
}
|
||||
var data = JSON.parse(IV.cache[index].content);
|
||||
var article = function (el) {
|
||||
return el.getElementsByTagName('article')[0];
|
||||
};
|
||||
var from = article(IV.findPageScroll());
|
||||
var to = article(IV.makeScrolledContent(data.html));
|
||||
morphdom(from, to, {
|
||||
onBeforeElUpdated: function (fromEl, toEl) {
|
||||
if (fromEl.classList.contains('loaded')) {
|
||||
toEl.classList.add('loaded');
|
||||
}
|
||||
return !fromEl.isEqualNode(toEl);
|
||||
}
|
||||
});
|
||||
IV.initMedia();
|
||||
eval(data.js);
|
||||
},
|
||||
loadPage: function (index) {
|
||||
if (!IV.cache[index]) {
|
||||
IV.cache[index] = {};
|
||||
}
|
||||
IV.cache[index].loading = true;
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onload = function () {
|
||||
IV.cache[index].loading = false;
|
||||
IV.cache[index].content = xhr.responseText;
|
||||
IV.applyUpdatedContent(index);
|
||||
if (IV.pending && IV.pending[0] == index) {
|
||||
IV.navigateToLoaded(index, IV.pending[1]);
|
||||
}
|
||||
if (IV.cache[index].reloadPending) {
|
||||
IV.cache[index].reloadPending = false;
|
||||
IV.reloadPage(index);
|
||||
}
|
||||
}
|
||||
|
||||
xhr.open('GET', 'page' + index + '.json');
|
||||
xhr.send();
|
||||
},
|
||||
reloadPage: function (index) {
|
||||
if (IV.cache[index] && IV.cache[index].loading) {
|
||||
IV.cache[index].reloadPending = true;
|
||||
return;
|
||||
}
|
||||
IV.loadPage(index);
|
||||
},
|
||||
|
||||
makeScrolledContent: function (html) {
|
||||
var result = document.createElement('div');
|
||||
result.className = 'page-scroll';
|
||||
result.tabIndex = '-1';
|
||||
result.innerHTML = '<div class="page-slide"><article>'
|
||||
+ html
|
||||
+ '</article></div>';
|
||||
result.onscroll = IV.frameScrolled;
|
||||
return result;
|
||||
},
|
||||
navigateToLoaded: function (index, hash) {
|
||||
if (IV.cache[index].dom) {
|
||||
IV.navigateToDOM(index, hash);
|
||||
} else {
|
||||
var data = JSON.parse(IV.cache[index].content);
|
||||
var el = document.createElement('div');
|
||||
el.className = 'page-scroll';
|
||||
el.tabIndex = '-1';
|
||||
el.innerHTML = '<div class="page-slide"><article>'
|
||||
+ data.html
|
||||
+ '</article></div>';
|
||||
el.onscroll = IV.frameScrolled;
|
||||
IV.cache[index].dom = el;
|
||||
IV.cache[index].dom = IV.makeScrolledContent(data.html);
|
||||
|
||||
IV.navigateToDOM(index, hash);
|
||||
eval(data.js);
|
||||
|
@ -417,6 +460,14 @@ var IV = {
|
|||
was.parentNode.appendChild(now);
|
||||
if (scroll !== undefined) {
|
||||
now.scrollTop = scroll;
|
||||
setTimeout(function () {
|
||||
// When returning by history.back to an URL with a hash
|
||||
// for the first time browser forces the scroll to the
|
||||
// hash instead of the saved scroll position.
|
||||
//
|
||||
// This workaround prevents incorrect scroll position.
|
||||
now.scrollTop = scroll;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
now.classList.add(back ? 'hidden-left' : 'hidden-right');
|
||||
|
@ -446,7 +497,12 @@ var IV = {
|
|||
topBack.classList.remove('hidden');
|
||||
}
|
||||
IV.index = index;
|
||||
IV.initMedia();
|
||||
if (IV.cache[index].contentUpdated) {
|
||||
IV.cache[index].contentUpdated = false;
|
||||
IV.applyUpdatedContent(index);
|
||||
} else {
|
||||
IV.initMedia();
|
||||
}
|
||||
if (scroll === undefined) {
|
||||
IV.jumpToHash(hash, true);
|
||||
} else {
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
<file alias="page.js">../../iv_html/page.js</file>
|
||||
<file alias="highlight.css">../../iv_html/highlight.9.12.0.css</file>
|
||||
<file alias="highlight.js">../../iv_html/highlight.9.12.0.js</file>
|
||||
<file alias="morphdom.js">../../iv_html/morphdom-umd.min.2.7.2.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -281,6 +281,7 @@ bool WebPageData::applyChanges(
|
|||
&& document == newDocument
|
||||
&& collage.items == newCollage.items
|
||||
&& (!iv == !newIv)
|
||||
&& (!iv || iv->partial() == newIv->partial())
|
||||
&& duration == newDuration
|
||||
&& author == resultAuthor
|
||||
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
||||
|
|
|
@ -161,9 +161,7 @@ namespace {
|
|||
<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();">
|
||||
|
@ -193,6 +191,11 @@ namespace {
|
|||
)"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray ReadResource(const QString &name) {
|
||||
auto file = QFile(u":/iv/"_q + name);
|
||||
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Controller::Controller()
|
||||
|
@ -225,11 +228,28 @@ void Controller::show(
|
|||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||
page.script = fillInChannelValuesScript(std::move(inChannelValues));
|
||||
_titleText.setText(st::ivTitle.style, page.title);
|
||||
_title->update();
|
||||
InvokeQueued(_container, [=, page = std::move(page)]() mutable {
|
||||
showInWindow(dataPath, std::move(page));
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::update(Prepared page) {
|
||||
const auto url = page.url;
|
||||
auto i = _indices.find(url);
|
||||
if (i == end(_indices)) {
|
||||
return;
|
||||
}
|
||||
const auto index = i->second;
|
||||
_pages[index] = std::move(page);
|
||||
|
||||
if (_ready) {
|
||||
_webview->eval(reloadScript(index));
|
||||
} else if (!index) {
|
||||
_reloadInitialWhenReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Controller::fillInChannelValuesScript(
|
||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||
auto result = QByteArray();
|
||||
|
@ -426,6 +446,9 @@ void Controller::createWebview(const QString &dataPath) {
|
|||
std::exchange(_navigateToIndexWhenReady, -1),
|
||||
base::take(_navigateToHashWhenReady));
|
||||
}
|
||||
if (base::take(_reloadInitialWhenReady)) {
|
||||
script += reloadScript(0);
|
||||
}
|
||||
if (!script.isEmpty()) {
|
||||
_webview->eval(script);
|
||||
}
|
||||
|
@ -501,10 +524,13 @@ void Controller::createWebview(const QString &dataPath) {
|
|||
const auto qstring = QString::fromUtf8(id.data(), id.size());
|
||||
const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q;
|
||||
if (QRegularExpression(pattern).match(qstring).hasMatch()) {
|
||||
auto file = QFile(u":/iv/"_q + qstring);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
const auto bytes = ReadResource(qstring);
|
||||
if (!bytes.isEmpty()) {
|
||||
const auto mime = css ? "text/css" : "text/javascript";
|
||||
return finishWith(file.readAll(), mime);
|
||||
const auto full = (qstring == u"page.js"_q)
|
||||
? (ReadResource("morphdom.js") + bytes)
|
||||
: bytes;
|
||||
return finishWith(full, mime);
|
||||
}
|
||||
}
|
||||
return Webview::DataResult::Failed;
|
||||
|
@ -560,6 +586,12 @@ QByteArray Controller::navigateScript(int index, const QString &hash) {
|
|||
+ "');";
|
||||
}
|
||||
|
||||
QByteArray Controller::reloadScript(int index) {
|
||||
return "IV.reloadPage("
|
||||
+ QByteArray::number(index)
|
||||
+ ");";
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -55,6 +55,8 @@ public:
|
|||
const QString &dataPath,
|
||||
Prepared page,
|
||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
||||
void update(Prepared page);
|
||||
|
||||
[[nodiscard]] bool active() const;
|
||||
void showJoinedTooltip();
|
||||
void minimize();
|
||||
|
@ -73,6 +75,7 @@ private:
|
|||
void createWindow();
|
||||
void createWebview(const QString &dataPath);
|
||||
[[nodiscard]] QByteArray navigateScript(int index, const QString &hash);
|
||||
[[nodiscard]] QByteArray reloadScript(int index);
|
||||
|
||||
void updateTitleGeometry();
|
||||
void paintTitle(Painter &p, QRect clip);
|
||||
|
@ -104,6 +107,7 @@ private:
|
|||
base::flat_map<QByteArray, bool> _inChannelChanged;
|
||||
base::flat_set<QByteArray> _inChannelSubscribed;
|
||||
SingleQueuedInvokation _updateStyles;
|
||||
bool _reloadInitialWhenReady = false;
|
||||
bool _subscribedToColors = false;
|
||||
bool _ready = false;
|
||||
|
||||
|
|
|
@ -55,6 +55,10 @@ QString Data::id() const {
|
|||
return qs(_source->page.data().vurl());
|
||||
}
|
||||
|
||||
bool Data::partial() const {
|
||||
return _source->page.data().is_part();
|
||||
}
|
||||
|
||||
Data::~Data() = default;
|
||||
|
||||
void Data::prepare(const Options &options, Fn<void(Prepared)> done) const {
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
~Data();
|
||||
|
||||
[[nodiscard]] QString id() const;
|
||||
[[nodiscard]] bool partial() const;
|
||||
|
||||
void prepare(const Options &options, Fn<void(Prepared)> done) const;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "iv/iv_instance.h"
|
||||
|
||||
#include "base/call_delayed.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/application.h"
|
||||
#include "core/file_utilities.h"
|
||||
|
@ -82,6 +83,7 @@ public:
|
|||
[[nodiscard]] bool active() const;
|
||||
|
||||
void moveTo(not_null<Data*> data, QString hash);
|
||||
void update(not_null<Data*> data);
|
||||
|
||||
void showJoinedTooltip();
|
||||
void minimize();
|
||||
|
@ -149,6 +151,7 @@ private:
|
|||
void sendEmbed(QByteArray hash, Webview::DataRequest request);
|
||||
|
||||
void fillChannelJoinedValues(const Prepared &result);
|
||||
void fillEmbeds(base::flat_map<QByteArray, QByteArray> added);
|
||||
void subscribeToDocuments();
|
||||
[[nodiscard]] QByteArray readFile(
|
||||
const std::shared_ptr<::Data::DocumentMedia> &media);
|
||||
|
@ -206,8 +209,8 @@ void Shown::prepare(not_null<Data*> data, const QString &hash) {
|
|||
return;
|
||||
}
|
||||
_preparing = false;
|
||||
_embeds = std::move(result.embeds);
|
||||
fillChannelJoinedValues(result);
|
||||
fillEmbeds(std::move(result.embeds));
|
||||
if (!base.isEmpty()) {
|
||||
_localBase = base;
|
||||
showLocal(std::move(result));
|
||||
|
@ -234,6 +237,16 @@ void Shown::fillChannelJoinedValues(const Prepared &result) {
|
|||
}
|
||||
}
|
||||
|
||||
void Shown::fillEmbeds(base::flat_map<QByteArray, QByteArray> added) {
|
||||
if (_embeds.empty()) {
|
||||
_embeds = std::move(added);
|
||||
} else {
|
||||
for (auto &[k, v] : added) {
|
||||
_embeds[k] = std::move(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Shown::showLocal(Prepared result) {
|
||||
showProgress(0);
|
||||
|
||||
|
@ -783,6 +796,23 @@ void Shown::moveTo(not_null<Data*> data, QString hash) {
|
|||
}
|
||||
}
|
||||
|
||||
void Shown::update(not_null<Data*> data) {
|
||||
const auto weak = base::make_weak(this);
|
||||
|
||||
const auto id = data->id();
|
||||
const auto base = /*local ? LookupLocalPath(show) : */QString();
|
||||
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
|
||||
crl::on_main(weak, [=, result = std::move(result)]() mutable {
|
||||
result.url = id;
|
||||
fillChannelJoinedValues(result);
|
||||
fillEmbeds(std::move(result.embeds));
|
||||
if (_controller) {
|
||||
_controller->update(std::move(result));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void Shown::showJoinedTooltip() {
|
||||
if (_controller) {
|
||||
_controller->showJoinedTooltip();
|
||||
|
@ -804,6 +834,13 @@ void Instance::show(
|
|||
not_null<Data*> data,
|
||||
QString hash) {
|
||||
const auto session = &show->session();
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (data->partial()) {
|
||||
base::call_delayed(10000, [=] {
|
||||
requestFull(session, data->id());
|
||||
});
|
||||
}
|
||||
});
|
||||
if (_shown && _shownSession == session) {
|
||||
_shown->moveTo(data, hash);
|
||||
return;
|
||||
|
@ -891,7 +928,9 @@ void Instance::show(
|
|||
) | rpl::start_with_next([=](const ::Data::PeerUpdate &update) {
|
||||
if (const auto channel = update.peer->asChannel()) {
|
||||
if (channel->amIn()) {
|
||||
if (_joining.remove(not_null(channel))) {
|
||||
const auto i = _joining.find(session);
|
||||
const auto value = not_null{ channel };
|
||||
if (i != end(_joining) && i->second.remove(value)) {
|
||||
_shown->showJoinedTooltip();
|
||||
}
|
||||
}
|
||||
|
@ -902,13 +941,8 @@ void Instance::show(
|
|||
_tracking.emplace(session);
|
||||
session->lifetime().add([=] {
|
||||
_tracking.remove(session);
|
||||
for (auto i = begin(_joining); i != end(_joining);) {
|
||||
if (&(*i)->session() == session) {
|
||||
i = _joining.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
_joining.remove(session);
|
||||
_fullRequested.remove(session);
|
||||
if (_shownSession == session) {
|
||||
_shownSession = nullptr;
|
||||
}
|
||||
|
@ -919,6 +953,27 @@ void Instance::show(
|
|||
}
|
||||
}
|
||||
|
||||
void Instance::requestFull(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &id) {
|
||||
if (!_tracking.contains(session)
|
||||
|| !_fullRequested[session].emplace(id).second) {
|
||||
return;
|
||||
}
|
||||
session->api().request(MTPmessages_GetWebPage(
|
||||
MTP_string(id),
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPmessages_WebPage &result) {
|
||||
session->data().processUsers(result.data().vusers());
|
||||
session->data().processChats(result.data().vchats());
|
||||
const auto page = session->data().processWebpage(
|
||||
result.data().vwebpage());
|
||||
if (page && page->iv && _shown && _shownSession == session) {
|
||||
_shown->update(page->iv.get());
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Instance::processOpenChannel(const QString &context) {
|
||||
if (!_shownSession) {
|
||||
return;
|
||||
|
@ -949,7 +1004,7 @@ void Instance::processJoinChannel(const QString &context) {
|
|||
return;
|
||||
} else if (const auto channelId = ChannelId(context.toLongLong())) {
|
||||
const auto channel = _shownSession->data().channel(channelId);
|
||||
_joining.emplace(channel);
|
||||
_joining[_shownSession].emplace(channel);
|
||||
if (channel->isLoaded()) {
|
||||
_shownSession->api().joinChannel(channel);
|
||||
} else if (!channel->username().isEmpty()) {
|
||||
|
|
|
@ -40,11 +40,17 @@ public:
|
|||
private:
|
||||
void processOpenChannel(const QString &context);
|
||||
void processJoinChannel(const QString &context);
|
||||
void requestFull(not_null<Main::Session*> session, const QString &id);
|
||||
|
||||
std::unique_ptr<Shown> _shown;
|
||||
Main::Session *_shownSession = nullptr;
|
||||
base::flat_set<not_null<Main::Session*>> _tracking;
|
||||
base::flat_set<not_null<ChannelData*>> _joining;
|
||||
base::flat_map<
|
||||
not_null<Main::Session*>,
|
||||
base::flat_set<not_null<ChannelData*>>> _joining;
|
||||
base::flat_map<
|
||||
not_null<Main::Session*>,
|
||||
base::flat_set<QString>> _fullRequested;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue