mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support complex history and anchors.
This commit is contained in:
parent
fae10cfa6b
commit
8b62c37c34
7 changed files with 369 additions and 68 deletions
|
@ -128,9 +128,33 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
bottom: -36px;
|
bottom: -36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-scroll {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.page-slide {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0%;
|
||||||
|
transition: margin 240ms ease-in-out;
|
||||||
|
}
|
||||||
|
.hidden-left,
|
||||||
|
.hidden-right {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.hidden-left .page-slide {
|
||||||
|
margin-left: -100%;
|
||||||
|
}
|
||||||
|
.hidden-right .page-slide {
|
||||||
|
margin-left: 100%;
|
||||||
|
}
|
||||||
article {
|
article {
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
overflow: hidden;
|
overflow-y: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
max-width: 732px;
|
max-width: 732px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
@ -150,6 +174,9 @@ article h2 {
|
||||||
margin: -6px 18px 12px;
|
margin: -6px 18px 12px;
|
||||||
color: var(--td-window-sub-text-fg);
|
color: var(--td-window-sub-text-fg);
|
||||||
}
|
}
|
||||||
|
article h5 {
|
||||||
|
margin: 21px 18px 12px;
|
||||||
|
}
|
||||||
article address {
|
article address {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: var(--td-window-sub-text-fg);
|
color: var(--td-window-sub-text-fg);
|
||||||
|
|
|
@ -35,20 +35,31 @@ var IV = {
|
||||||
context: context,
|
context: context,
|
||||||
});
|
});
|
||||||
} else if (target.hash.length < 2) {
|
} else if (target.hash.length < 2) {
|
||||||
IV.hash = '';
|
IV.jumpToHash('');
|
||||||
IV.scrollTo(0);
|
|
||||||
} else {
|
} else {
|
||||||
const name = target.hash.substr(1);
|
IV.jumpToHash(target.hash.substr(1));
|
||||||
IV.hash = name;
|
|
||||||
|
|
||||||
const element = document.getElementsByName(name)[0];
|
|
||||||
if (element) {
|
|
||||||
const y = element.getBoundingClientRect().y;
|
|
||||||
IV.scrollTo(y + document.documentElement.scrollTop);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
},
|
||||||
|
jumpToHash: function (hash, instant) {
|
||||||
|
var current = IV.computeCurrentState();
|
||||||
|
current.hash = hash;
|
||||||
|
window.history.replaceState(current, '');
|
||||||
|
if (hash == '') {
|
||||||
|
IV.scrollTo(0, instant);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementsByName(hash)[0];
|
||||||
|
if (element) {
|
||||||
|
var y = 0;
|
||||||
|
while (element && !element.classList.contains('page-scroll')) {
|
||||||
|
y += element.offsetTop;
|
||||||
|
element = element.offsetParent;
|
||||||
|
}
|
||||||
|
IV.scrollTo(y, instant);
|
||||||
|
}
|
||||||
|
},
|
||||||
frameKeyDown: function (e) {
|
frameKeyDown: function (e) {
|
||||||
const keyW = (e.key === 'w')
|
const keyW = (e.key === 'w')
|
||||||
|| (e.code === 'KeyW')
|
|| (e.code === 'KeyW')
|
||||||
|
@ -210,7 +221,7 @@ var IV = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init: function () {
|
init: function () {
|
||||||
IV.hash = window.location.hash.substr(1);
|
window.history.replaceState(IV.computeCurrentState(), '');
|
||||||
|
|
||||||
const buttons = document.getElementsByClassName('fixed_button');
|
const buttons = document.getElementsByClassName('fixed_button');
|
||||||
for (let i = 0; i < buttons.length; ++i) {
|
for (let i = 0; i < buttons.length; ++i) {
|
||||||
|
@ -228,6 +239,10 @@ var IV = {
|
||||||
IV.stopRipples(e.currentTarget);
|
IV.stopRipples(e.currentTarget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
IV.initMedia();
|
||||||
|
IV.notify({ event: 'ready' });
|
||||||
|
},
|
||||||
|
initMedia: function () {
|
||||||
const photos = document.getElementsByClassName('photo');
|
const photos = document.getElementsByClassName('photo');
|
||||||
for (let i = 0; i < photos.length; ++i) {
|
for (let i = 0; i < photos.length; ++i) {
|
||||||
const photo = photos[i];
|
const photo = photos[i];
|
||||||
|
@ -258,7 +273,6 @@ var IV = {
|
||||||
video.classList.add('loaded');
|
video.classList.add('loaded');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
IV.notify({ event: 'ready' });
|
|
||||||
},
|
},
|
||||||
showTooltip: function (text) {
|
showTooltip: function (text) {
|
||||||
var toast = document.createElement('div');
|
var toast = document.createElement('div');
|
||||||
|
@ -272,14 +286,163 @@ var IV = {
|
||||||
document.body.removeChild(toast);
|
document.body.removeChild(toast);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
},
|
},
|
||||||
scrollTo: function (y) {
|
scrollTo: function (y, instant) {
|
||||||
document.getElementById('bottom_up').classList.add('hidden');
|
document.getElementById('bottom_up').classList.add('hidden');
|
||||||
window.scrollTo({ top: y || 0, behavior: 'smooth' });
|
IV.findPageScroll().scrollTo({
|
||||||
|
top: y || 0,
|
||||||
|
behavior: instant ? 'instant' : 'smooth'
|
||||||
|
});
|
||||||
},
|
},
|
||||||
menu: function (button) {
|
menu: function (button) {
|
||||||
IV.frozenRipple = button.id;
|
IV.frozenRipple = button.id;
|
||||||
IV.notify({ event: 'menu', hash: IV.hash });
|
const state = this.computeCurrentState();
|
||||||
}
|
IV.notify({ event: 'menu', index: state.index, hash: state.hash });
|
||||||
|
},
|
||||||
|
|
||||||
|
computeCurrentState: function () {
|
||||||
|
var now = IV.findPageScroll();
|
||||||
|
return {
|
||||||
|
position: IV.position,
|
||||||
|
index: IV.index,
|
||||||
|
hash: ((!window.history.state
|
||||||
|
|| window.history.state.hash === undefined)
|
||||||
|
? window.location.hash.substr(1)
|
||||||
|
: window.history.state.hash),
|
||||||
|
scroll: now ? now.scrollTop : 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
navigateTo: function (index, hash) {
|
||||||
|
if (!index && !IV.index) {
|
||||||
|
IV.navigateToDOM(IV.index, hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
} else if (IV.cache[index].dom) {
|
||||||
|
IV.navigateToDOM(index, hash);
|
||||||
|
} else if (IV.cache[index].content) {
|
||||||
|
IV.navigateToLoaded(index, hash);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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.innerHTML = '<div class="page-slide"><article>'
|
||||||
|
+ data.html
|
||||||
|
+ '</article></div>';
|
||||||
|
IV.cache[index].dom = el;
|
||||||
|
|
||||||
|
IV.navigateToDOM(index, hash);
|
||||||
|
eval(data.js);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigateToDOM: function (index, hash) {
|
||||||
|
IV.pending = null;
|
||||||
|
if (IV.index == index) {
|
||||||
|
IV.jumpToHash(hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.history.replaceState(IV.computeCurrentState(), '');
|
||||||
|
|
||||||
|
IV.position = IV.position + 1;
|
||||||
|
window.history.pushState(
|
||||||
|
{ position: IV.position, index: index, hash: hash },
|
||||||
|
'',
|
||||||
|
'page' + index + '.html' + (hash.length ? '#' + hash : ''));
|
||||||
|
IV.showDOM(index, hash);
|
||||||
|
},
|
||||||
|
findPageScroll: function () {
|
||||||
|
var all = document.getElementsByClassName('page-scroll');
|
||||||
|
for (i = 0; i < all.length; ++i) {
|
||||||
|
if (!all[i].classList.contains('hidden-left')
|
||||||
|
&& !all[i].classList.contains('hidden-right')) {
|
||||||
|
return all[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
showDOM: function (index, hash, scroll) {
|
||||||
|
IV.pending = null;
|
||||||
|
if (IV.index != index) {
|
||||||
|
var initial = !window.history.state
|
||||||
|
|| window.history.state.position === undefined;
|
||||||
|
var back = initial
|
||||||
|
|| IV.position > window.history.state.position;
|
||||||
|
IV.position = initial ? 0 : window.history.state.position;
|
||||||
|
|
||||||
|
var now = IV.cache[index].dom;
|
||||||
|
var was = IV.findPageScroll();
|
||||||
|
if (!IV.cache[IV.index]) {
|
||||||
|
IV.cache[IV.index] = {};
|
||||||
|
}
|
||||||
|
IV.cache[IV.index].dom = was;
|
||||||
|
was.parentNode.appendChild(now);
|
||||||
|
if (scroll !== undefined) {
|
||||||
|
now.scrollTop = scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
now.classList.add(back ? 'hidden-left' : 'hidden-right');
|
||||||
|
now.classList.remove(back ? 'hidden-right' : 'hidden-left');
|
||||||
|
now.firstChild.getAnimations().forEach(
|
||||||
|
(animation) => animation.finish());
|
||||||
|
|
||||||
|
if (!was.listening) {
|
||||||
|
was.listening = true;
|
||||||
|
was.firstChild.addEventListener('transitionend', function (e) {
|
||||||
|
if (was.classList.contains('hidden-left')
|
||||||
|
|| was.classList.contains('hidden-right')) {
|
||||||
|
if (was.parentNode) {
|
||||||
|
was.parentNode.removeChild(was);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
was.classList.add(back ? 'hidden-right' : 'hidden-left');
|
||||||
|
now.classList.remove(back ? 'hidden-left' : 'hidden-right');
|
||||||
|
|
||||||
|
var topBack = document.getElementById('top_back');
|
||||||
|
if (!IV.position) {
|
||||||
|
topBack.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
topBack.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
IV.index = index;
|
||||||
|
IV.initMedia();
|
||||||
|
if (scroll === undefined) {
|
||||||
|
IV.jumpToHash(hash, true);
|
||||||
|
}
|
||||||
|
} else if (scroll !== undefined) {
|
||||||
|
IV.scrollTo(scroll);
|
||||||
|
} else {
|
||||||
|
IV.jumpToHash(hash);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
back: function () {
|
||||||
|
window.history.back();
|
||||||
|
},
|
||||||
|
|
||||||
|
cache: {},
|
||||||
|
index: 0,
|
||||||
|
position: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
document.onclick = IV.frameClickHandler;
|
document.onclick = IV.frameClickHandler;
|
||||||
|
@ -288,3 +451,9 @@ document.onmouseenter = IV.frameMouseEnter;
|
||||||
document.onmouseup = IV.frameMouseUp;
|
document.onmouseup = IV.frameMouseUp;
|
||||||
document.onscroll = IV.frameScrolled;
|
document.onscroll = IV.frameScrolled;
|
||||||
window.onmessage = IV.postMessageHandler;
|
window.onmessage = IV.postMessageHandler;
|
||||||
|
|
||||||
|
window.addEventListener('popstate', function (e) {
|
||||||
|
if (e.state) {
|
||||||
|
IV.showDOM(e.state.index, e.state.hash, e.state.scroll);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtCore/QJsonValue>
|
#include <QtCore/QJsonValue>
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
#include <QtGui/QPainter>
|
#include <QtGui/QPainter>
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
namespace Iv {
|
namespace Iv {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -130,9 +131,7 @@ namespace {
|
||||||
.replace('\'', "\\\'");
|
.replace('\'', "\\\'");
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QByteArray WrapPage(
|
[[nodiscard]] QByteArray WrapPage(const Prepared &page) {
|
||||||
const Prepared &page,
|
|
||||||
const QByteArray &initScript) {
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
const auto classAttribute = ""_q;
|
const auto classAttribute = ""_q;
|
||||||
#else // Q_OS_MAC
|
#else // Q_OS_MAC
|
||||||
|
@ -143,7 +142,7 @@ namespace {
|
||||||
+ (page.hasCode ? "IV.initPreBlocks();" : "")
|
+ (page.hasCode ? "IV.initPreBlocks();" : "")
|
||||||
+ (page.hasEmbeds ? "IV.initEmbedBlocks();" : "")
|
+ (page.hasEmbeds ? "IV.initEmbedBlocks();" : "")
|
||||||
+ "IV.init();"
|
+ "IV.init();"
|
||||||
+ initScript;
|
+ page.script;
|
||||||
|
|
||||||
const auto contentAttributes = page.rtl
|
const auto contentAttributes = page.rtl
|
||||||
? " dir=\"rtl\" class=\"rtl\""_q
|
? " dir=\"rtl\" class=\"rtl\""_q
|
||||||
|
@ -183,7 +182,9 @@ namespace {
|
||||||
<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>
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<article)"_q + contentAttributes + ">"_q + page.content + R"(</article>
|
<div class="page-scroll"><div class="page-slide">
|
||||||
|
<article)"_q + contentAttributes + ">"_q + page.content + R"(</article>
|
||||||
|
</div></div>
|
||||||
<script>)"_q + js + R"(</script>
|
<script>)"_q + js + R"(</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -199,28 +200,29 @@ Controller::Controller()
|
||||||
_webview->eval("IV.updateStyles('" + str + "');");
|
_webview->eval("IV.updateStyles('" + str + "');");
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
createWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::~Controller() {
|
Controller::~Controller() {
|
||||||
|
_window->hide();
|
||||||
_ready = false;
|
_ready = false;
|
||||||
_webview = nullptr;
|
_webview = nullptr;
|
||||||
_title = nullptr;
|
_title = nullptr;
|
||||||
_window = nullptr;
|
_window = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Controller::showFast(const QString &url, const QString &hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::show(
|
void Controller::show(
|
||||||
const QString &dataPath,
|
const QString &dataPath,
|
||||||
Prepared page,
|
Prepared page,
|
||||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||||
createWindow();
|
page.script = fillInChannelValuesScript(std::move(inChannelValues));
|
||||||
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), js);
|
showInWindow(dataPath, std::move(page));
|
||||||
if (!_webview) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,13 +230,15 @@ QByteArray Controller::fillInChannelValuesScript(
|
||||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||||
auto result = QByteArray();
|
auto result = QByteArray();
|
||||||
for (auto &[id, in] : inChannelValues) {
|
for (auto &[id, in] : inChannelValues) {
|
||||||
std::move(in) | rpl::start_with_next([=](bool in) {
|
if (_inChannelSubscribed.emplace(id).second) {
|
||||||
if (_ready) {
|
std::move(in) | rpl::start_with_next([=](bool in) {
|
||||||
_webview->eval(toggleInChannelScript(id, in));
|
if (_ready) {
|
||||||
} else {
|
_webview->eval(toggleInChannelScript(id, in));
|
||||||
_inChannelChanged[id] = in;
|
} else {
|
||||||
}
|
_inChannelChanged[id] = in;
|
||||||
}, _lifetime);
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const auto &[id, in] : base::take(_inChannelChanged)) {
|
for (const auto &[id, in] : base::take(_inChannelChanged)) {
|
||||||
result += toggleInChannelScript(id, in);
|
result += toggleInChannelScript(id, in);
|
||||||
|
@ -342,14 +346,10 @@ void Controller::createWindow() {
|
||||||
window->show();
|
window->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::showInWindow(
|
void Controller::createWebview(const QString &dataPath) {
|
||||||
const QString &dataPath,
|
Expects(!_webview);
|
||||||
Prepared page,
|
|
||||||
const QByteArray &initScript) {
|
|
||||||
Expects(_container != nullptr);
|
|
||||||
|
|
||||||
const auto window = _window.get();
|
const auto window = _window.get();
|
||||||
_url = page.url;
|
|
||||||
_webview = std::make_unique<Webview::Window>(
|
_webview = std::make_unique<Webview::Window>(
|
||||||
_container,
|
_container,
|
||||||
Webview::WindowConfig{
|
Webview::WindowConfig{
|
||||||
|
@ -362,10 +362,7 @@ void Controller::showInWindow(
|
||||||
_ready = false;
|
_ready = false;
|
||||||
_webview = nullptr;
|
_webview = nullptr;
|
||||||
});
|
});
|
||||||
if (!raw->widget()) {
|
|
||||||
_events.fire({ Event::Type::Close });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window->events(
|
window->events(
|
||||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||||
if (e->type() == QEvent::Close) {
|
if (e->type() == QEvent::Close) {
|
||||||
|
@ -411,11 +408,18 @@ void Controller::showInWindow(
|
||||||
for (const auto &[id, in] : base::take(_inChannelChanged)) {
|
for (const auto &[id, in] : base::take(_inChannelChanged)) {
|
||||||
script += toggleInChannelScript(id, in);
|
script += toggleInChannelScript(id, in);
|
||||||
}
|
}
|
||||||
|
if (_navigateToIndexWhenReady >= 0) {
|
||||||
|
script += navigateScript(
|
||||||
|
std::exchange(_navigateToIndexWhenReady, -1),
|
||||||
|
base::take(_navigateToHashWhenReady));
|
||||||
|
}
|
||||||
if (!script.isEmpty()) {
|
if (!script.isEmpty()) {
|
||||||
_webview->eval(script);
|
_webview->eval(script);
|
||||||
}
|
}
|
||||||
} else if (event == u"menu"_q) {
|
} else if (event == u"menu"_q) {
|
||||||
menu(object.value("hash").toString());
|
menu(
|
||||||
|
object.value("index").toInt(),
|
||||||
|
object.value("hash").toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -433,7 +437,7 @@ void Controller::showInWindow(
|
||||||
return Webview::DataResult::Done;
|
return Webview::DataResult::Done;
|
||||||
};
|
};
|
||||||
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.starts_with("page") && id.ends_with(".html")) {
|
||||||
if (!_subscribedToColors) {
|
if (!_subscribedToColors) {
|
||||||
_subscribedToColors = true;
|
_subscribedToColors = true;
|
||||||
|
|
||||||
|
@ -444,7 +448,33 @@ void Controller::showInWindow(
|
||||||
_updateStyles.call();
|
_updateStyles.call();
|
||||||
}, _webview->lifetime());
|
}, _webview->lifetime());
|
||||||
}
|
}
|
||||||
return finishWith(WrapPage(page, initScript), "text/html");
|
auto index = 0;
|
||||||
|
const auto result = std::from_chars(
|
||||||
|
id.data() + 4,
|
||||||
|
id.data() + id.size() - 5,
|
||||||
|
index);
|
||||||
|
if (result.ec != std::errc()
|
||||||
|
|| index < 0
|
||||||
|
|| index >= _pages.size()) {
|
||||||
|
return Webview::DataResult::Failed;
|
||||||
|
}
|
||||||
|
return finishWith(WrapPage(_pages[index]), "text/html");
|
||||||
|
} else if (id.starts_with("page") && id.ends_with(".json")) {
|
||||||
|
auto index = 0;
|
||||||
|
const auto result = std::from_chars(
|
||||||
|
id.data() + 4,
|
||||||
|
id.data() + id.size() - 5,
|
||||||
|
index);
|
||||||
|
if (result.ec != std::errc()
|
||||||
|
|| index < 0
|
||||||
|
|| index >= _pages.size()) {
|
||||||
|
return Webview::DataResult::Failed;
|
||||||
|
}
|
||||||
|
auto &page = _pages[index];
|
||||||
|
return finishWith(QJsonDocument(QJsonObject{
|
||||||
|
{ "html", QJsonValue(QString::fromUtf8(page.content)) },
|
||||||
|
{ "js", QJsonValue(QString::fromUtf8(page.script)) },
|
||||||
|
}).toJson(QJsonDocument::Compact), "application/json");
|
||||||
}
|
}
|
||||||
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");
|
||||||
|
@ -464,12 +494,48 @@ void Controller::showInWindow(
|
||||||
});
|
});
|
||||||
|
|
||||||
raw->init(R"()");
|
raw->init(R"()");
|
||||||
|
}
|
||||||
|
|
||||||
auto id = u"iv/page.html"_q;
|
void Controller::showInWindow(const QString &dataPath, Prepared page) {
|
||||||
if (!page.hash.isEmpty()) {
|
Expects(_container != nullptr);
|
||||||
id += '#' + page.hash;
|
|
||||||
|
const auto url = page.url;
|
||||||
|
const auto hash = page.hash;
|
||||||
|
auto i = _indices.find(url);
|
||||||
|
if (i == end(_indices)) {
|
||||||
|
_pages.push_back(std::move(page));
|
||||||
|
i = _indices.emplace(url, int(_pages.size() - 1)).first;
|
||||||
}
|
}
|
||||||
raw->navigateToData(id);
|
const auto index = i->second;
|
||||||
|
if (!_webview) {
|
||||||
|
createWebview(dataPath);
|
||||||
|
if (_webview && _webview->widget()) {
|
||||||
|
auto id = u"iv/page%1.html"_q.arg(index);
|
||||||
|
if (!hash.isEmpty()) {
|
||||||
|
id += '#' + hash;
|
||||||
|
}
|
||||||
|
_webview->navigateToData(id);
|
||||||
|
} else {
|
||||||
|
_events.fire({ Event::Type::Close });
|
||||||
|
}
|
||||||
|
} else if (_ready) {
|
||||||
|
_webview->eval(navigateScript(index, hash));
|
||||||
|
_window->activateWindow();
|
||||||
|
_window->setFocus();
|
||||||
|
} else {
|
||||||
|
_navigateToIndexWhenReady = index;
|
||||||
|
_navigateToHashWhenReady = hash;
|
||||||
|
_window->activateWindow();
|
||||||
|
_window->setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Controller::navigateScript(int index, const QString &hash) {
|
||||||
|
return "IV.navigateTo("
|
||||||
|
+ QByteArray::number(index)
|
||||||
|
+ ", '"
|
||||||
|
+ EscapeForScriptString(hash.toUtf8())
|
||||||
|
+ "');";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::processKey(const QString &key, const QString &modifier) {
|
void Controller::processKey(const QString &key, const QString &modifier) {
|
||||||
|
@ -537,8 +603,8 @@ void Controller::minimize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::menu(const QString &hash) {
|
void Controller::menu(int index, const QString &hash) {
|
||||||
if (!_webview || _menu) {
|
if (!_webview || _menu || index < 0 || index > _pages.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
@ -552,7 +618,8 @@ void Controller::menu(const QString &hash) {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const auto url = _url + (hash.isEmpty() ? u""_q : ('#' + hash));
|
const auto url = _pages[index].url
|
||||||
|
+ (hash.isEmpty() ? u""_q : ('#' + hash));
|
||||||
const auto openInBrowser = crl::guard(_window.get(), [=] {
|
const auto openInBrowser = crl::guard(_window.get(), [=] {
|
||||||
_events.fire({ .type = Event::Type::OpenLinkExternal, .url = url });
|
_events.fire({ .type = Event::Type::OpenLinkExternal, .url = url });
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,6 +50,7 @@ public:
|
||||||
QString context;
|
QString context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] bool showFast(const QString &url, const QString &hash);
|
||||||
void show(
|
void show(
|
||||||
const QString &dataPath,
|
const QString &dataPath,
|
||||||
Prepared page,
|
Prepared page,
|
||||||
|
@ -70,12 +71,12 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void createWindow();
|
void createWindow();
|
||||||
|
void createWebview(const QString &dataPath);
|
||||||
|
[[nodiscard]] QByteArray navigateScript(int index, const QString &hash);
|
||||||
|
|
||||||
void updateTitleGeometry();
|
void updateTitleGeometry();
|
||||||
void paintTitle(Painter &p, QRect clip);
|
void paintTitle(Painter &p, QRect clip);
|
||||||
void showInWindow(
|
void showInWindow(const QString &dataPath, Prepared page);
|
||||||
const QString &dataPath,
|
|
||||||
Prepared page,
|
|
||||||
const QByteArray &initScript);
|
|
||||||
[[nodiscard]] QByteArray fillInChannelValuesScript(
|
[[nodiscard]] QByteArray fillInChannelValuesScript(
|
||||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
||||||
[[nodiscard]] QByteArray toggleInChannelScript(
|
[[nodiscard]] QByteArray toggleInChannelScript(
|
||||||
|
@ -85,7 +86,7 @@ private:
|
||||||
void processKey(const QString &key, const QString &modifier);
|
void processKey(const QString &key, const QString &modifier);
|
||||||
void processLink(const QString &url, const QString &context);
|
void processLink(const QString &url, const QString &context);
|
||||||
|
|
||||||
void menu(const QString &hash);
|
void menu(int index, const QString &hash);
|
||||||
void escape();
|
void escape();
|
||||||
void close();
|
void close();
|
||||||
void quit();
|
void quit();
|
||||||
|
@ -101,11 +102,16 @@ private:
|
||||||
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;
|
base::flat_map<QByteArray, bool> _inChannelChanged;
|
||||||
|
base::flat_set<QByteArray> _inChannelSubscribed;
|
||||||
SingleQueuedInvokation _updateStyles;
|
SingleQueuedInvokation _updateStyles;
|
||||||
QString _url;
|
|
||||||
bool _subscribedToColors = false;
|
bool _subscribedToColors = false;
|
||||||
bool _ready = false;
|
bool _ready = false;
|
||||||
|
|
||||||
|
std::vector<Prepared> _pages;
|
||||||
|
base::flat_map<QString, int> _indices;
|
||||||
|
QString _navigateToHashWhenReady;
|
||||||
|
int _navigateToIndexWhenReady = -1;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct Options {
|
||||||
struct Prepared {
|
struct Prepared {
|
||||||
QString title;
|
QString title;
|
||||||
QByteArray content;
|
QByteArray content;
|
||||||
|
QByteArray script;
|
||||||
QString url;
|
QString url;
|
||||||
QString hash;
|
QString hash;
|
||||||
std::vector<QByteArray> resources;
|
std::vector<QByteArray> resources;
|
||||||
|
|
|
@ -81,6 +81,8 @@ 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 moveTo(not_null<Data*> data, QString hash);
|
||||||
|
|
||||||
void showJoinedTooltip();
|
void showJoinedTooltip();
|
||||||
void minimize();
|
void minimize();
|
||||||
|
|
||||||
|
@ -115,6 +117,9 @@ private:
|
||||||
std::vector<Webview::DataRequest> requests;
|
std::vector<Webview::DataRequest> requests;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void prepare(not_null<Data*> data, const QString &hash);
|
||||||
|
void createController();
|
||||||
|
|
||||||
void showLocal(Prepared result);
|
void showLocal(Prepared result);
|
||||||
void showWindowed(Prepared result);
|
void showWindowed(Prepared result);
|
||||||
|
|
||||||
|
@ -163,6 +168,8 @@ private:
|
||||||
base::flat_map<DocumentId, FileLoad> _files;
|
base::flat_map<DocumentId, FileLoad> _files;
|
||||||
base::flat_map<QByteArray, rpl::producer<bool>> _inChannelValues;
|
base::flat_map<QByteArray, rpl::producer<bool>> _inChannelValues;
|
||||||
|
|
||||||
|
bool _preparing = false;
|
||||||
|
|
||||||
QString _localBase;
|
QString _localBase;
|
||||||
base::flat_map<QByteArray, QByteArray> _embeds;
|
base::flat_map<QByteArray, QByteArray> _embeds;
|
||||||
base::flat_map<QString, MapPreview> _maps;
|
base::flat_map<QString, MapPreview> _maps;
|
||||||
|
@ -181,15 +188,24 @@ Shown::Shown(
|
||||||
not_null<Data*> data,
|
not_null<Data*> data,
|
||||||
QString hash)
|
QString hash)
|
||||||
: _session(&show->session())
|
: _session(&show->session())
|
||||||
, _show(show)
|
, _show(show) {
|
||||||
, _id(data->id()) {
|
prepare(data, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::prepare(not_null<Data*> data, const QString &hash) {
|
||||||
const auto weak = base::make_weak(this);
|
const auto weak = base::make_weak(this);
|
||||||
|
|
||||||
|
_preparing = true;
|
||||||
|
const auto id = _id = data->id();
|
||||||
const auto base = /*local ? LookupLocalPath(show) : */QString();
|
const auto base = /*local ? LookupLocalPath(show) : */QString();
|
||||||
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
|
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
|
||||||
result.hash = hash;
|
result.hash = hash;
|
||||||
crl::on_main(weak, [=, result = std::move(result)]() mutable {
|
crl::on_main(weak, [=, result = std::move(result)]() mutable {
|
||||||
result.url = _id;
|
result.url = id;
|
||||||
|
if (_id != id || !_preparing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_preparing = false;
|
||||||
_embeds = std::move(result.embeds);
|
_embeds = std::move(result.embeds);
|
||||||
fillChannelJoinedValues(result);
|
fillChannelJoinedValues(result);
|
||||||
if (!base.isEmpty()) {
|
if (!base.isEmpty()) {
|
||||||
|
@ -416,7 +432,9 @@ void Shown::writeEmbed(QString id, QString hash) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shown::showWindowed(Prepared result) {
|
void Shown::createController() {
|
||||||
|
Expects(!_controller);
|
||||||
|
|
||||||
_controller = std::make_unique<Controller>();
|
_controller = std::make_unique<Controller>();
|
||||||
|
|
||||||
_controller->events(
|
_controller->events(
|
||||||
|
@ -436,6 +454,12 @@ void Shown::showWindowed(Prepared result) {
|
||||||
sendEmbed(id.mid(5).toUtf8(), std::move(request));
|
sendEmbed(id.mid(5).toUtf8(), std::move(request));
|
||||||
}
|
}
|
||||||
}, _controller->lifetime());
|
}, _controller->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::showWindowed(Prepared result) {
|
||||||
|
if (!_controller) {
|
||||||
|
createController();
|
||||||
|
}
|
||||||
|
|
||||||
const auto domain = &_session->domain();
|
const auto domain = &_session->domain();
|
||||||
_controller->show(
|
_controller->show(
|
||||||
|
@ -753,6 +777,12 @@ bool Shown::active() const {
|
||||||
return _controller && _controller->active();
|
return _controller && _controller->active();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shown::moveTo(not_null<Data*> data, QString hash) {
|
||||||
|
if (!_controller || !_controller->showFast(data->id(), hash)) {
|
||||||
|
prepare(data, hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Shown::showJoinedTooltip() {
|
void Shown::showJoinedTooltip() {
|
||||||
if (_controller) {
|
if (_controller) {
|
||||||
_controller->showJoinedTooltip();
|
_controller->showJoinedTooltip();
|
||||||
|
@ -774,7 +804,8 @@ void Instance::show(
|
||||||
not_null<Data*> data,
|
not_null<Data*> data,
|
||||||
QString hash) {
|
QString hash) {
|
||||||
const auto session = &show->session();
|
const auto session = &show->session();
|
||||||
if (_shown && _shown->showing(session, data)) {
|
if (_shown && _shownSession == session) {
|
||||||
|
_shown->moveTo(data, hash);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_shown = std::make_unique<Shown>(show, data, hash);
|
_shown = std::make_unique<Shown>(show, data, hash);
|
||||||
|
|
|
@ -744,7 +744,7 @@ QByteArray Parser::block(const MTPDpageBlockAudio &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::block(const MTPDpageBlockKicker &data) {
|
QByteArray Parser::block(const MTPDpageBlockKicker &data) {
|
||||||
return tag("h6", { { "class", "kicker" } }, rich(data.vtext()));
|
return tag("h5", { { "class", "kicker" } }, rich(data.vtext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Parser::block(const MTPDpageBlockTable &data) {
|
QByteArray Parser::block(const MTPDpageBlockTable &data) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue