/* global NexT, CONFIG */ HTMLElement.prototype.outerHeight = function(flag) { var height = this.offsetHeight; if (!flag) return height; var style = window.getComputedStyle(this); height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10); return height; }; HTMLElement.prototype.wrap = function(wrapper) { this.parentNode.insertBefore(wrapper, this); this.parentNode.removeChild(this); wrapper.appendChild(this); }; NexT.utils = { /** * Wrap images with fancybox. */ wrapImageWithFancyBox: function() { document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(element => { var $image = $(element); var imageLink = $image.attr('data-src') || $image.attr('src'); var $imageWrapLink = $image.wrap(``).parent('a'); if ($image.is('.post-gallery img')) { $imageWrapLink.addClass('post-gallery-img'); $imageWrapLink.attr('data-fancybox', 'gallery').attr('rel', 'gallery'); } else if ($image.is('.group-picture img')) { $imageWrapLink.attr('data-fancybox', 'group').attr('rel', 'group'); } else { $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default'); } var imageTitle = $image.attr('title') || $image.attr('alt'); if (imageTitle) { $imageWrapLink.append(`

${imageTitle}

`); // Make sure img title tag will show correctly in fancybox $imageWrapLink.attr('title', imageTitle).attr('data-caption', imageTitle); } }); $.fancybox.defaults.hash = false; $('.fancybox').fancybox({ loop : true, helpers: { overlay: { locked: false } } }); }, registerExtURL: function() { document.querySelectorAll('.exturl').forEach(element => { element.addEventListener('click', event => { var exturl = event.currentTarget.getAttribute('data-url'); var decurl = decodeURIComponent(escape(window.atob(exturl))); window.open(decurl, '_blank', 'noopener'); return false; }); }); }, /** * One-click copy code support. */ registerCopyCode: function() { document.querySelectorAll('figure.highlight').forEach(e => { const initButton = button => { if (CONFIG.copycode.style === 'mac') { button.innerHTML = ''; } else { button.innerText = CONFIG.translation.copy_button; } }; const box = document.createElement('div'); box.classList.add('highlight-wrap'); e.wrap(box); e.parentNode.insertAdjacentHTML('beforeend', '
'); var button = e.parentNode.querySelector('.copy-btn'); button.addEventListener('click', event => { var target = event.currentTarget; var code = [...target.parentNode.querySelectorAll('.code .line')].map(element => { return element.innerText; }).join('\n'); var ta = document.createElement('textarea'); var yPosition = window.scrollY; ta.style.top = yPosition + 'px'; // Prevent page scrolling ta.style.position = 'absolute'; ta.style.opacity = '0'; ta.readOnly = true; ta.value = code; document.body.append(ta); const selection = document.getSelection(); const selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false; ta.select(); ta.setSelectionRange(0, code.length); ta.readOnly = false; var result = document.execCommand('copy'); if (CONFIG.copycode.show_result) { target.innerText = result ? CONFIG.translation.copy_success : CONFIG.translation.copy_failure; } ta.blur(); // For iOS target.blur(); if (selected) { selection.removeAllRanges(); selection.addRange(selected); } document.body.removeChild(ta); }); button.addEventListener('mouseleave', event => { setTimeout(() => { initButton(event.target); }, 300); }); initButton(button); }); }, wrapTableWithBox: function() { document.querySelectorAll('table').forEach(table => { const box = document.createElement('div'); box.className = 'table-container'; table.wrap(box); }); }, registerVideoIframe: function() { document.querySelectorAll('iframe').forEach(element => { const SUPPORTED_PLAYERS = [ 'www.youtube.com', 'player.vimeo.com', 'player.youku.com', 'player.bilibili.com', 'www.tudou.com' ]; const pattern = new RegExp(SUPPORTED_PLAYERS.join('|')); if (!element.parentNode.matches('.video-container') && element.src.search(pattern) > 0) { const box = document.createElement('div'); box.className = 'video-container'; element.wrap(box); let width = Number(element.width); let height = Number(element.height); if (width && height) { element.parentNode.style.paddingTop = (height / width * 100) + '%'; } } }); }, registerScrollPercent: function() { var THRESHOLD = 50; var backToTop = document.querySelector('.back-to-top'); var readingProgressBar = document.querySelector('.reading-progress-bar'); // For init back to top in sidebar if page was scrolled after page refresh. window.addEventListener('scroll', () => { var scrollPercent; if (backToTop || readingProgressBar) { var docHeight = document.querySelector('.container').offsetHeight; var winHeight = window.innerHeight; var contentVisibilityHeight = docHeight > winHeight ? docHeight - winHeight : document.body.scrollHeight - winHeight; var scrollPercentRounded = Math.round(100 * window.scrollY / contentVisibilityHeight); scrollPercent = Math.min(scrollPercentRounded, 100) + '%'; } if (backToTop) { window.scrollY > THRESHOLD ? backToTop.classList.add('back-to-top-on') : backToTop.classList.remove('back-to-top-on'); backToTop.querySelector('span').innerText = scrollPercent; } if (readingProgressBar) { readingProgressBar.style.width = scrollPercent; } }); backToTop && backToTop.addEventListener('click', () => { window.anime({ targets : [document.documentElement, document.body], duration : 500, easing : 'linear', scrollTop: 0 }); }); }, /** * Tabs tag listener (without twitter bootstrap). */ registerTabsTag: function() { // Binding `nav-tabs` & `tab-content` by real time permalink changing. document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(tab => { tab.addEventListener('click', event => { event.preventDefault(); var target = event.currentTarget; // Prevent selected tab to select again. if (!target.classList.contains('active')) { // Add & Remove active class on `nav-tabs` & `tab-content`. [...target.parentNode.children].forEach(item => { item.classList.remove('active'); }); target.classList.add('active'); var tActive = document.getElementById(target.querySelector('a').getAttribute('href').replace('#', '')); [...tActive.parentNode.children].forEach(item => { item.classList.remove('active'); }); tActive.classList.add('active'); // Trigger event tActive.dispatchEvent(new Event('tabs:click', { bubbles: true })); } }); }); window.dispatchEvent(new Event('tabs:register')); }, registerCanIUseTag: function() { // Get responsive height passed from iframe. window.addEventListener('message', e => { var data = e.data; if ((typeof data === 'string') && (data.indexOf('ciu_embed') > -1)) { var featureID = data.split(':')[1]; var height = data.split(':')[2]; document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 'px'; } }, false); }, registerActiveMenuItem: function() { document.querySelectorAll('.menu-item').forEach(element => { var target = element.querySelector('a[href]'); if (!target) return; var isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); var isSubPath = target.pathname !== CONFIG.root && location.pathname.indexOf(target.pathname) === 0; if (target.hostname === location.hostname && (isSamePath || isSubPath)) { element.classList.add('menu-item-active'); } else { element.classList.remove('menu-item-active'); } }); }, registerSidebarTOC: function() { const navItems = document.querySelectorAll('.post-toc li'); const sections = [...navItems].map(element => { var link = element.querySelector('a.nav-link'); // TOC item animation navigate. link.addEventListener('click', event => { event.preventDefault(); var target = document.getElementById(event.currentTarget.getAttribute('href').replace('#', '')); var offset = target.getBoundingClientRect().top + window.scrollY; window.anime({ targets : [document.documentElement, document.body], duration : 500, easing : 'linear', scrollTop: offset + 10 }); }); return document.getElementById(link.getAttribute('href').replace('#', '')); }); var tocElement = document.querySelector('.post-toc-wrap'); function activateNavByIndex(target) { if (target.classList.contains('active-current')) return; document.querySelectorAll('.post-toc .active').forEach(element => { element.classList.remove('active', 'active-current'); }); target.classList.add('active', 'active-current'); var parent = target.parentNode; while (!parent.matches('.post-toc')) { if (parent.matches('li')) parent.classList.add('active'); parent = parent.parentNode; } // Scrolling to center active TOC element if TOC content is taller then viewport. window.anime({ targets : tocElement, duration : 200, easing : 'linear', scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top }); } function findIndex(entries) { let index = 0; let entry = entries[index]; if (entry.boundingClientRect.top > 0) { index = sections.indexOf(entry.target); return index === 0 ? 0 : index - 1; } for (;index < entries.length; index++) { if (entries[index].boundingClientRect.top <= 0) { entry = entries[index]; } else { return sections.indexOf(entry.target); } } return sections.indexOf(entry.target); } function createIntersectionObserver(marginTop) { marginTop = Math.floor(marginTop + 10000); let intersectionObserver = new IntersectionObserver((entries, observe) => { let scrollHeight = document.documentElement.scrollHeight + 100; if (scrollHeight > marginTop) { observe.disconnect(); createIntersectionObserver(scrollHeight); return; } let index = findIndex(entries); activateNavByIndex(navItems[index]); }, { rootMargin: marginTop + 'px 0px -100% 0px', threshold : 0 }); sections.forEach(item => intersectionObserver.observe(item)); } createIntersectionObserver(document.documentElement.scrollHeight); }, hasMobileUA: function() { var ua = navigator.userAgent; var pa = /iPad|iPhone|Android|Opera Mini|BlackBerry|webOS|UCWEB|Blazer|PSP|IEMobile|Symbian/g; return pa.test(ua); }, isTablet: function() { return window.screen.width < 992 && window.screen.width > 767 && this.hasMobileUA(); }, isMobile: function() { return window.screen.width < 767 && this.hasMobileUA(); }, isDesktop: function() { return !this.isTablet() && !this.isMobile(); }, isMuse: function() { return CONFIG.scheme === 'Muse'; }, isMist: function() { return CONFIG.scheme === 'Mist'; }, isPisces: function() { return CONFIG.scheme === 'Pisces'; }, isGemini: function() { return CONFIG.scheme === 'Gemini'; }, /** * Init Sidebar & TOC inner dimensions on all pages and for all schemes. * Need for Sidebar/TOC inner scrolling if content taller then viewport. */ initSidebarDimension: function() { var sidebarNav = document.querySelector('.sidebar-nav'); var sidebarNavHeight = sidebarNav.style.display !== 'none' ? sidebarNav.outerHeight(true) : 0; var sidebarOffset = CONFIG.sidebar.offset || 12; var sidebarb2tHeight = CONFIG.back2top.enable && CONFIG.back2top.sidebar ? document.querySelector('.back-to-top').offsetHeight : 0; var sidebarSchemePadding = CONFIG.sidebarPadding + sidebarNavHeight + sidebarb2tHeight; // Margin of sidebar b2t: 8px -10px -20px, brings a different of 12px. if (NexT.utils.isPisces() || NexT.utils.isGemini()) sidebarSchemePadding += (sidebarOffset * 2) - 12; // Initialize Sidebar & TOC Height. var sidebarWrapperHeight = document.body.offsetHeight - sidebarSchemePadding + 'px'; document.querySelector('.site-overview-wrap').style.maxHeight = sidebarWrapperHeight; document.querySelector('.post-toc-wrap').style.maxHeight = sidebarWrapperHeight; }, updateSidebarPosition: function() { var sidebarNav = document.querySelector('.sidebar-nav'); var hasTOC = document.querySelector('.post-toc'); if (hasTOC) { sidebarNav.style.display = ''; sidebarNav.classList.add('motion-element'); document.querySelector('.sidebar-nav-toc').click(); } else { sidebarNav.style.display = 'none'; sidebarNav.classList.remove('motion-element'); document.querySelector('.sidebar-nav-overview').click(); } NexT.utils.initSidebarDimension(); if (!this.isDesktop() || this.isPisces() || this.isGemini()) return; // Expand sidebar on post detail page by default, when post has a toc. var display = CONFIG.page.sidebar; if (typeof display !== 'boolean') { // There's no definition sidebar in the page front-matter. display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); } if (display) { window.dispatchEvent(new Event('sidebar:show')); } }, getScript: function(url, callback, condition) { if (condition) { callback(); } else { var script = document.createElement('script'); script.onload = script.onreadystatechange = function(_, isAbort) { if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) { script.onload = script.onreadystatechange = null; script = undefined; if (!isAbort && callback) setTimeout(callback, 0); } }; script.src = url; document.head.appendChild(script); } } };