| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- var IV = {
- notify: function(message) {
- if (window.external && window.external.invoke) {
- window.external.invoke(JSON.stringify(message));
- }
- },
- frameClickHandler: function(e) {
- var target = e.target;
- var context = '';
- while (target) {
- if (target.id == 'menu_page_blocker') {
- IV.notify({ event: 'menu_page_blocker_click' });
- IV.menuShown(false);
- return;
- }
- if (target.tagName == 'AUDIO' || target.tagName == 'VIDEO') {
- return;
- }
- if (context === ''
- && target.hasAttribute
- && target.hasAttribute('data-context')) {
- context = String(target.getAttribute('data-context'));
- }
- if (target.tagName == 'A') {
- break;
- }
- target = target.parentNode;
- }
- if (!target || (context === '' && !target.hasAttribute('href'))) {
- return;
- }
- var base = document.createElement('A');
- base.href = window.location.href;
- if (base.origin != target.origin
- || base.pathname != target.pathname
- || base.search != target.search) {
- IV.notify({
- event: 'link_click',
- url: target.href,
- context: context,
- });
- } else if (target.hash.length < 2) {
- IV.jumpToHash('');
- } else {
- IV.jumpToHash(decodeURIComponent(target.hash.substr(1)));
- }
- e.preventDefault();
- },
- getElementTop: function (element) {
- var top = 0;
- while (element && !element.classList.contains('page-scroll')) {
- top += element.offsetTop;
- element = element.offsetParent;
- }
- return top;
- },
- jumpToHash: function (hash, instant) {
- var current = IV.computeCurrentState();
- current.hash = hash;
- window.history.replaceState(
- current,
- '',
- 'page' + IV.index + '.html');
- if (hash == '') {
- IV.scrollTo(0, instant);
- return;
- }
- var element = document.getElementsByName(hash)[0];
- if (element) {
- IV.scrollTo(IV.getElementTop(element), instant);
- }
- },
- frameKeyDown: function (e) {
- const key0 = (e.key === '0')
- || (e.code === 'Key0')
- || (e.keyCode === 48);
- const keyW = (e.key === 'w')
- || (e.code === 'KeyW')
- || (e.keyCode === 87);
- const keyQ = (e.key === 'q')
- || (e.code === 'KeyQ')
- || (e.keyCode === 81);
- const keyM = (e.key === 'm')
- || (e.code === 'KeyM')
- || (e.keyCode === 77);
- if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
- e.preventDefault();
- IV.notify({
- event: 'keydown',
- modifier: e.ctrlKey ? 'ctrl' : 'cmd',
- key: key0 ? '0' : keyW ? 'w' : keyQ ? 'q' : 'm',
- });
- } else if (e.key === 'Escape' || e.keyCode === 27) {
- e.preventDefault();
- if (IV.position) {
- window.history.back();
- } else {
- IV.notify({
- event: 'keydown',
- key: 'escape',
- });
- }
- }
- },
- frameMouseEnter: function (e) {
- IV.notify({ event: 'mouseenter' });
- },
- frameMouseUp: function (e) {
- IV.notify({ event: 'mouseup' });
- },
- lastScrollTop: 0,
- frameScrolled: function (e) {
- const was = IV.lastScrollTop;
- IV.lastScrollTop = IV.findPageScroll().scrollTop;
- IV.updateJumpToTop(was < IV.lastScrollTop);
- IV.checkVideos();
- },
- updateJumpToTop: function (scrolledDown) {
- if (IV.lastScrollTop < 100) {
- document.getElementById('bottom_up').classList.add('hidden');
- } else if (scrolledDown && IV.lastScrollTop > 200) {
- document.getElementById('bottom_up').classList.remove('hidden');
- }
- },
- updateStyles: function (styles) {
- if (IV.styles !== styles) {
- IV.styles = styles;
- document.getElementsByTagName('html')[0].style = styles;
- }
- },
- toggleChannelJoined: function (id, joined) {
- IV.channelsJoined['channel' + id] = joined;
- IV.checkChannelButtons();
- },
- checkChannelButtons: function() {
- const channels = document.getElementsByClassName('channel');
- for (var i = 0; i < channels.length; ++i) {
- const channel = channels[i];
- const full = String(channel.getAttribute('data-context'));
- const value = IV.channelsJoined[full];
- if (value !== undefined) {
- channel.classList.toggle('joined', value);
- }
- }
- },
- slideshowSlide: function(el, delta) {
- var dir = window.getComputedStyle(el, null).direction || 'ltr';
- var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
- if (delta) {
- var form = el.parentNode.firstChild;
- var s = form.s;
- const next = +s.value + delta;
- s.value = (next == s.length) ? 0 : (next == -1) ? (s.length - 1) : next;
- form.nextSibling.firstChild.style[marginProp] = (-100 * s.value) + '%';
- } else {
- el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%';
- }
- return false;
- },
- initPreBlocks: function() {
- if (!hljs) {
- return;
- }
- var pres = document.getElementsByTagName('pre');
- for (var i = 0; i < pres.length; i++) {
- if (pres[i].hasAttribute('data-language')) {
- hljs.highlightBlock(pres[i]);
- }
- }
- },
- initEmbedBlocks: function() {
- var iframes = document.getElementsByTagName('iframe');
- for (var i = 0; i < iframes.length; i++) {
- (function(iframe) {
- window.addEventListener('message', function(event) {
- if (event.source !== iframe.contentWindow ||
- event.origin != window.origin) {
- return;
- }
- try {
- var data = JSON.parse(event.data);
- } catch(e) {
- var data = {};
- }
- if (data.eventType == 'resize_frame') {
- if (data.eventData.height) {
- iframe.style.height = data.eventData.height + 'px';
- }
- }
- }, false);
- })(iframes[i]);
- }
- },
- addRipple: function (button, x, y) {
- const ripple = document.createElement('span');
- ripple.classList.add('ripple');
- const inner = document.createElement('span');
- inner.classList.add('inner');
- x -= button.offsetLeft;
- y -= button.offsetTop;
- const mx = button.clientWidth - x;
- const my = button.clientHeight - y;
- const sq1 = x * x + y * y;
- const sq2 = mx * mx + y * y;
- const sq3 = x * x + my * my;
- const sq4 = mx * mx + my * my;
- const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4));
- inner.style.width = inner.style.height = `${2 * radius}px`;
- inner.style.left = `${x - radius}px`;
- inner.style.top = `${y - radius}px`;
- inner.classList.add('inner');
- ripple.addEventListener('animationend', function (e) {
- if (e.animationName === 'fadeOut') {
- ripple.remove();
- }
- });
- ripple.appendChild(inner);
- button.appendChild(ripple);
- },
- stopRipples: function (button) {
- const id = button.id ? button.id : button;
- button = document.getElementById(id);
- const ripples = button.getElementsByClassName('ripple');
- for (var i = 0; i < ripples.length; ++i) {
- const ripple = ripples[i];
- if (!ripple.classList.contains('hiding')) {
- ripple.classList.add('hiding');
- }
- }
- },
- init: function () {
- var current = IV.computeCurrentState();
- window.history.replaceState(current, '', IV.pageUrl(0));
- IV.jumpToHash(current.hash, true);
- IV.lastScrollTop = window.history.state.scroll;
- IV.findPageScroll().onscroll = IV.frameScrolled;
- const buttons = document.getElementsByClassName('fixed_button');
- for (let i = 0; i < buttons.length; ++i) {
- const button = buttons[i];
- button.addEventListener('mousedown', function (e) {
- IV.addRipple(e.currentTarget, e.clientX, e.clientY);
- });
- button.addEventListener('mouseup', function (e) {
- const id = e.currentTarget.id;
- setTimeout(function () {
- IV.stopRipples(id);
- }, 0);
- });
- button.addEventListener('mouseleave', function (e) {
- IV.stopRipples(e.currentTarget);
- });
- }
- IV.initMedia();
- IV.notify({ event: 'ready' });
- IV.forceScrollFocus();
- IV.frameScrolled();
- },
- initMedia: function () {
- var scroll = IV.findPageScroll();
- const photos = scroll.getElementsByClassName('photo');
- for (let i = 0; i < photos.length; ++i) {
- const photo = photos[i];
- if (photo.classList.contains('loaded')) {
- continue;
- }
- const url = photo.style.backgroundImage;
- if (!url || url.length < 7) {
- continue;
- }
- var img = new Image();
- img.onload = function () {
- photo.classList.add('loaded');
- }
- img.src = url.substr(5, url.length - 7);
- if (img.complete) {
- photo.classList.add('loaded');
- IV.stopAnimations(photo);
- }
- }
- IV.videos = [];
- const videos = scroll.getElementsByClassName('video');
- for (let i = 0; i < videos.length; ++i) {
- const element = videos[i];
- IV.videos.push({
- element: element,
- src: String(element.getAttribute('data-src')),
- autoplay: (element.getAttribute('data-autoplay') == '1'),
- loop: (element.getAttribute('data-loop') == '1'),
- small: (element.getAttribute('data-small') == '1'),
- filled: (element.firstChild
- && element.firstChild.tagName == 'VIDEO'),
- });
- }
- },
- checkVideos: function () {
- const visibleTop = IV.lastScrollTop;
- const visibleBottom = visibleTop + IV.findPageScroll().offsetHeight;
- const videos = IV.videos;
- for (let i = 0; i < videos.length; ++i) {
- const video = videos[i];
- const element = video.element;
- const wrap = element.offsetParent; // video-wrap
- const top = IV.getElementTop(wrap);
- const bottom = top + wrap.offsetHeight;
- if (top < visibleBottom && bottom > visibleTop) {
- if (!video.created) {
- video.created = new Date();
- video.loaded = false;
- element.innerHTML = '<video muted class="'
- + (video.small ? 'video-small' : '')
- + '"'
- + (video.autoplay
- ? ' preload="auto" autoplay'
- : (video.small
- ? ''
- : ' controls'))
- + (video.loop ? ' loop' : '')
- + ' oncanplay="IV.checkVideos();"'
- + ' onloadeddata="IV.checkVideos();">'
- + '<source src="'
- + video.src
- + '" type="video/mp4" />'
- + '</video>';
- var media = element.firstChild;
- media.oncontextmenu = function () { return false; };
- media.oncanplay = IV.checkVideos;
- media.onloadeddata = IV.checkVideos;
- }
- } else if (video.created && video.autoplay) {
- video.created = false;
- element.innerHTML = '';
- }
- if (video.created && !video.loaded) {
- var media = element.firstChild;
- const HAVE_CURRENT_DATA = 2;
- if (media && media.readyState >= HAVE_CURRENT_DATA) {
- video.loaded = true;
- media.classList.add('loaded');
- if ((new Date() - video.created) < 100) {
- IV.stopAnimations(media);
- }
- }
- }
- }
- },
- 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);
- },
- scrollTo: function (y, instant) {
- if (y < 200) {
- document.getElementById('bottom_up').classList.add('hidden');
- }
- IV.findPageScroll().scrollTo({
- top: y || 0,
- behavior: instant ? 'instant' : 'smooth'
- });
- },
- 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
- };
- },
- pageUrl: function (index, hash) {
- var result = 'page' + index + '.html';
- if (hash) {
- result += '#' + hash;
- }
- return result;
- },
- navigateTo: function (index, hash) {
- if (!index && !IV.index) {
- IV.navigateToDOM(IV.index, hash);
- return;
- }
- IV.pending = [index, hash];
- if (!IV.cache[index]) {
- 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 footer = function (el) {
- return el.getElementsByClassName('page-footer')[0];
- };
- var from = IV.findPageScroll();
- var to = IV.makeScrolledContent(data.html);
- morphdom(article(from), article(to), {
- onBeforeElUpdated: function (fromEl, toEl) {
- if (fromEl.classList.contains('video')
- && toEl.classList.contains('video')
- && fromEl.hasAttribute('data-src')
- && toEl.hasAttribute('data-src')
- && (fromEl.getAttribute('data-src')
- == toEl.getAttribute('data-src'))) {
- return false;
- } else if (fromEl.tagName == 'SECTION'
- && fromEl.classList.contains('channel')
- && fromEl.hasAttribute('data-context')
- && toEl.tagName == 'SECTION'
- && toEl.classList.contains('channel')
- && toEl.hasAttribute('data-context')
- && (String(fromEl.getAttribute('data-context'))
- == String(toEl.getAttribute('data-context')))) {
- return false;
- } else if (fromEl.classList.contains('loaded')) {
- toEl.classList.add('loaded');
- }
- return !fromEl.isEqualNode(toEl);
- }
- });
- morphdom(footer(from), footer(to));
- 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 = html.trim();
- 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);
- IV.cache[index].dom = IV.makeScrolledContent(data.html);
- IV.navigateToDOM(index, hash);
- eval(data.js);
- }
- },
- navigateToDOM: function (index, hash) {
- IV.pending = null;
- if (IV.index == index) {
- IV.jumpToHash(hash);
- IV.forceScrollFocus();
- return;
- }
- window.history.replaceState(
- IV.computeCurrentState(),
- '',
- IV.pageUrl(IV.index));
- IV.position = IV.position + 1;
- window.history.pushState(
- { position: IV.position, index: index, hash: hash },
- '',
- IV.pageUrl(index));
- 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;
- 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');
- now.classList.remove(back ? 'hidden-right' : 'hidden-left');
- IV.stopAnimations(now.firstChild);
- 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);
- var videos = was.getElementsByClassName('video');
- for (var i = 0; i < videos.length; ++i) {
- videos[i].innerHTML = '';
- }
- }
- }
- });
- }
- was.classList.add(back ? 'hidden-right' : 'hidden-left');
- now.classList.remove(back ? 'hidden-left' : 'hidden-right');
- IV.index = index;
- IV.notify({
- event: 'location_change',
- index: IV.index,
- position: IV.position,
- hash: IV.computeCurrentState().hash,
- });
- if (IV.cache[index].contentUpdated) {
- IV.cache[index].contentUpdated = false;
- IV.applyUpdatedContent(index);
- } else {
- IV.initMedia();
- }
- IV.checkChannelButtons();
- if (scroll === undefined) {
- IV.jumpToHash(hash, true);
- } else {
- IV.lastScrollTop = scroll;
- IV.updateJumpToTop(true);
- }
- } else if (scroll !== undefined) {
- IV.scrollTo(scroll);
- IV.lastScrollTop = scroll;
- IV.updateJumpToTop(true);
- } else {
- IV.jumpToHash(hash);
- }
- IV.forceScrollFocus();
- IV.frameScrolled();
- },
- forceScrollFocus: function () {
- IV.findPageScroll().focus();
- setTimeout(function () {
- // Doesn't work on #hash-ed pages in Windows WebView2 otherwise.
- IV.findPageScroll().focus();
- }, 100);
- },
- stopAnimations: function (element) {
- element.getAnimations().forEach(
- (animation) => animation.finish());
- },
- menuShown: function (shown) {
- var already = document.getElementById('menu_page_blocker');
- if (already && shown) {
- return;
- } else if (already) {
- document.body.removeChild(already);
- return;
- } else if (!shown) {
- return;
- }
- var blocker = document.createElement('div');
- blocker.id = 'menu_page_blocker';
- document.body.appendChild(blocker);
- },
- videos: {},
- videosPlaying: {},
- cache: {},
- channelsJoined: {},
- index: 0,
- position: 0
- };
- document.onclick = IV.frameClickHandler;
- document.onkeydown = IV.frameKeyDown;
- document.onmouseenter = IV.frameMouseEnter;
- document.onmouseup = IV.frameMouseUp;
- document.onresize = IV.checkVideos;
- window.onmessage = IV.postMessageHandler;
- window.addEventListener('popstate', function (e) {
- if (e.state) {
- IV.showDOM(e.state.index, e.state.hash, e.state.scroll);
- }
- });
- document.addEventListener("DOMContentLoaded", IV.forceScrollFocus);
|