123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- /* global NexT, CONFIG */
- 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(`<a class="fancybox fancybox.image" href="${imageLink}" itemscope itemtype="http://schema.org/ImageObject" itemprop="url"></a>`).parent('a');
- if ($image.is('.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(`<p class="image-caption">${imageTitle}</p>`);
- // 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('span.exturl').forEach(element => {
- let link = document.createElement('a');
- // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
- link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => {
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
- }).join(''));
- link.rel = 'noopener external nofollow noreferrer';
- link.target = '_blank';
- link.className = element.className;
- link.innerHTML = element.innerHTML;
- element.parentNode.replaceChild(link, element);
- });
- },
- /**
- * One-click copy code support.
- */
- registerCopyCode: function() {
- document.querySelectorAll('figure.highlight').forEach(element => {
- const box = document.createElement('div');
- element.wrap(box);
- box.classList.add('highlight-container');
- box.insertAdjacentHTML('beforeend', '<div class="copy-btn"><i class="fa fa-clipboard"></i></div>');
- var button = element.parentNode.querySelector('.copy-btn');
- button.addEventListener('click', event => {
- var target = event.currentTarget;
- var code = [...target.parentNode.querySelectorAll('.code .line')].map(line => line.innerText).join('\n');
- var ta = document.createElement('textarea');
- ta.style.top = window.scrollY + '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.querySelector('i').className = result ? 'fa fa-check' : 'fa fa-times';
- }
- ta.blur(); // For iOS
- target.blur();
- if (selected) {
- selection.removeAllRanges();
- selection.addRange(selected);
- }
- document.body.removeChild(ta);
- });
- button.addEventListener('mouseleave', event => {
- setTimeout(() => {
- event.target.querySelector('i').className = 'fa fa-clipboard';
- }, 300);
- });
- });
- },
- wrapTableWithBox: function() {
- document.querySelectorAll('table').forEach(element => {
- const box = document.createElement('div');
- box.className = 'table-container';
- element.wrap(box);
- });
- },
- registerVideoIframe: function() {
- document.querySelectorAll('iframe').forEach(element => {
- const supported = [
- 'www.youtube.com',
- 'player.vimeo.com',
- 'player.youku.com',
- 'player.bilibili.com',
- 'www.tudou.com'
- ].some(host => element.src.includes(host));
- if (supported && !element.parentNode.matches('.video-container')) {
- 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', () => {
- if (backToTop || readingProgressBar) {
- var docHeight = document.querySelector('.container').offsetHeight;
- var winHeight = window.innerHeight;
- var contentVisibilityHeight = docHeight > winHeight ? docHeight - winHeight : document.body.scrollHeight - winHeight;
- var scrollPercent = Math.min(100 * window.scrollY / contentVisibilityHeight, 100);
- if (backToTop) {
- backToTop.classList.toggle('back-to-top-on', window.scrollY > THRESHOLD);
- backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%';
- }
- if (readingProgressBar) {
- readingProgressBar.style.width = scrollPercent.toFixed(2) + '%';
- }
- }
- });
- backToTop && backToTop.addEventListener('click', () => {
- window.anime({
- targets : document.scrollingElement,
- 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(element => {
- element.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(element => {
- element.classList.remove('active');
- });
- target.classList.add('active');
- var tActive = document.getElementById(target.querySelector('a').getAttribute('href').replace('#', ''));
- [...tActive.parentNode.children].forEach(element => {
- element.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', ({ data }) => {
- if ((typeof data === 'string') && data.includes('ciu_embed')) {
- var featureID = data.split(':')[1];
- var height = data.split(':')[2];
- document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + '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 = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname);
- element.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath));
- });
- },
- registerLangSelect: function() {
- let sel = document.querySelector('.lang-select');
- if (!sel) return;
- sel.value = CONFIG.page.lang;
- sel.addEventListener('change', () => {
- let target = sel.options[sel.selectedIndex];
- document.querySelector('.lang-select-label span').innerText = target.text;
- let url = target.dataset.href;
- window.pjax ? window.pjax.loadUrl(url) : window.location.href = url;
- });
- },
- 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.scrollingElement,
- 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(element => {
- element && intersectionObserver.observe(element);
- });
- }
- createIntersectionObserver(document.documentElement.scrollHeight);
- },
- hasMobileUA: function() {
- let ua = navigator.userAgent;
- let 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();
- },
- supportsPDFs: function() {
- let ua = navigator.userAgent;
- let isFirefoxWithPDFJS = ua.includes('irefox') && parseInt(ua.split('rv:')[1].split('.')[0], 10) > 18;
- let supportsPdfMimeType = typeof navigator.mimeTypes['application/pdf'] !== 'undefined';
- let isIOS = /iphone|ipad|ipod/i.test(ua.toLowerCase());
- return isFirefoxWithPDFJS || (supportsPdfMimeType && !isIOS);
- },
- /**
- * 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.offsetHeight : 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.sidebar.padding * 2) + sidebarNavHeight + sidebarb2tHeight;
- // Margin of sidebar b2t: -4px -10px -18px, brings a different of 22px.
- if (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') sidebarSchemePadding += (sidebarOffset * 2) - 22;
- // 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() || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') 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);
- }
- },
- loadComments: function(element, callback) {
- if (!CONFIG.comments.lazyload || !element) {
- callback();
- return;
- }
- let intersectionObserver = new IntersectionObserver((entries, observer) => {
- let entry = entries[0];
- if (entry.isIntersecting) {
- callback();
- observer.disconnect();
- }
- });
- intersectionObserver.observe(element);
- return intersectionObserver;
- }
- };
|