1/* global NexT, CONFIG */ 2 3NexT.utils = NexT.$u = { 4 5 /** 6 * Wrap images with fancybox support. 7 */ 8 wrapImageWithFancyBox: function() { 9 $('.content img') 10 .not('[hidden]') 11 .not('.group-picture img, .post-gallery img') 12 .each(function() { 13 var $image = $(this); 14 var imageTitle = $image.attr('title'); 15 var $imageWrapLink = $image.parent('a'); 16 17 if ($imageWrapLink.length < 1) { 18 var imageLink = $image.attr('data-original') ? this.getAttribute('data-original') : this.getAttribute('src'); 19 $imageWrapLink = $image.wrap('<a data-fancybox="group" href="' + imageLink + '"></a>').parent('a'); 20 } 21 22 $imageWrapLink.addClass('fancybox fancybox.image'); 23 $imageWrapLink.attr('rel', 'group'); 24 25 if (imageTitle) { 26 $imageWrapLink.append('<p class="image-caption">' + imageTitle + '</p>'); 27 28 //make sure img title tag will show correctly in fancybox 29 $imageWrapLink.attr('title', imageTitle); 30 } 31 }); 32 33 $('.fancybox').fancybox({ 34 helpers: { 35 overlay: { 36 locked: false 37 } 38 } 39 }); 40 }, 41 42 lazyLoadPostsImages: function() { 43 $('#posts').find('img').lazyload({ 44 //placeholder: '/images/loading.gif', 45 effect : 'fadeIn', 46 threshold: 0 47 }); 48 }, 49 50 /** 51 * Tabs tag listener (without twitter bootstrap). 52 */ 53 registerTabsTag: function() { 54 var tNav = '.tabs ul.nav-tabs '; 55 56 // Binding `nav-tabs` & `tab-content` by real time permalink changing. 57 $(function() { 58 $(window).bind('hashchange', function() { 59 var tHash = location.hash; 60 if (tHash !== '' && !tHash.match(/%\S{2}/)) { 61 $(tNav + 'li:has(a[href="' + tHash + '"])').addClass('active').siblings().removeClass('active'); 62 $(tHash).addClass('active').siblings().removeClass('active'); 63 } 64 }).trigger('hashchange'); 65 }); 66 67 $(tNav + '.tab').on('click', function(href) { 68 href.preventDefault(); 69 // Prevent selected tab to select again. 70 if (!$(this).hasClass('active')) { 71 72 // Add & Remove active class on `nav-tabs` & `tab-content`. 73 $(this).addClass('active').siblings().removeClass('active'); 74 var tActive = $(this).find('a').attr('href'); 75 $(tActive).addClass('active').siblings().removeClass('active'); 76 77 // Clear location hash in browser if #permalink exists. 78 if (location.hash !== '') { 79 history.pushState('', document.title, window.location.pathname + window.location.search); 80 } 81 } 82 }); 83 84 }, 85 86 registerESCKeyEvent: function() { 87 $(document).on('keyup', function(event) { 88 var shouldDismissSearchPopup = event.which === 27 89 && $('.search-popup').is(':visible'); 90 if (shouldDismissSearchPopup) { 91 $('.search-popup').hide(); 92 $('.search-popup-overlay').remove(); 93 $('body').css('overflow', ''); 94 } 95 }); 96 }, 97 98 registerBackToTop: function() { 99 var THRESHOLD = 50; 100 var $top = $('.back-to-top'); 101 102 function initBackToTop() { 103 $top.toggleClass('back-to-top-on', window.pageYOffset > THRESHOLD); 104 105 var scrollTop = $(window).scrollTop(); 106 var contentVisibilityHeight = NexT.utils.getContentVisibilityHeight(); 107 var scrollPercent = scrollTop / contentVisibilityHeight; 108 var scrollPercentRounded = Math.round(scrollPercent * 100); 109 var scrollPercentMaxed = scrollPercentRounded > 100 ? 100 : scrollPercentRounded; 110 $('#scrollpercent>span').html(scrollPercentMaxed); 111 } 112 113 // For init back to top in sidebar if page was scrolled after page refresh. 114 $(window).on('load', function() { 115 initBackToTop(); 116 }); 117 118 $(window).on('scroll', function() { 119 initBackToTop(); 120 }); 121 122 $top.on('click', function() { 123 $('body').velocity('scroll'); 124 }); 125 }, 126 127 /** 128 * Transform embedded video to support responsive layout. 129 * @see http://toddmotto.com/fluid-and-responsive-youtube-and-vimeo-videos-with-fluidvids-js/ 130 */ 131 embeddedVideoTransformer: function() { 132 var $iframes = $('iframe'); 133
134 // Supported Players. Extend this if you need more players. 135 var SUPPORTED_PLAYERS = [ 136 'www.youtube.com', 137 'player.vimeo.com', 138 'player.youku.com', 139 'music.163.com', 140 'www.tudou.com' 141 ]; 142 var pattern = new RegExp(SUPPORTED_PLAYERS.join('|')); 143 144 function getDimension($element) { 145 return { 146 width : $element.width(), 147 height: $element.height() 148 }; 149 } 150 151 function getAspectRadio(width, height) { 152 return height / width * 100; 153 } 154 155 $iframes.each(function() { 156 var iframe = this; 157 var $iframe = $(this); 158 var oldDimension = getDimension($iframe); 159 var newDimension; 160 161 if (this.src.search(pattern) > 0) { 162 163 // Calculate the video ratio based on the iframe's w/h dimensions 164 var videoRatio = getAspectRadio(oldDimension.width, oldDimension.height); 165 166 // Replace the iframe's dimensions and position the iframe absolute 167 // This is the trick to emulate the video ratio 168 $iframe.width('100%').height('100%') 169 .css({ 170 position: 'absolute', 171 top : '0', 172 left : '0' 173 }); 174 175 // Wrap the iframe in a new <div> which uses a dynamically fetched padding-top property 176 // based on the video's w/h dimensions 177 var wrap = document.createElement('div'); 178 wrap.className = 'fluid-vids'; 179 wrap.style.position = 'relative'; 180 wrap.style.marginBottom = '20px'; 181 wrap.style.width = '100%'; 182 wrap.style.paddingTop = videoRatio + '%'; 183 // Fix for appear inside tabs tag. 184 (wrap.style.paddingTop === '') && (wrap.style.paddingTop = '50%'); 185 186 // Add the iframe inside our newly created <div> 187 var iframeParent = iframe.parentNode; 188 iframeParent.insertBefore(wrap, iframe); 189 wrap.appendChild(iframe); 190 191 // Additional adjustments for 163 Music 192 if (this.src.search('music.163.com') > 0) { 193 newDimension = getDimension($iframe); 194 var shouldRecalculateAspect = newDimension.width > oldDimension.width 195 || newDimension.height < oldDimension.height; 196 197 // 163 Music Player has a fixed height, so we need to reset the aspect radio 198 if (shouldRecalculateAspect) { 199 wrap.style.paddingTop = getAspectRadio(newDimension.width, oldDimension.height) + '%'; 200 } 201 } 202 } 203 }); 204 205 }, 206 207 hasMobileUA: function() { 208 var nav = window.navigator; 209 var ua = nav.userAgent; 210 var pa = /iPad|iPhone|Android|Opera Mini|BlackBerry|webOS|UCWEB|Blazer|PSP|IEMobile|Symbian/g; 211 212 return pa.test(ua); 213 }, 214 215 isTablet: function() { 216 return window.screen.width < 992 && window.screen.width > 767 && this.hasMobileUA(); 217 }, 218 219 isMobile: function() { 220 return window.screen.width < 767 && this.hasMobileUA(); 221 }, 222 223 isDesktop: function() { 224 return !this.isTablet() && !this.isMobile(); 225 }, 226 227 /** 228 * Escape meta symbols in jQuery selectors. 229 * 230 * @param selector 231 * @returns {string|void|XML|*} 232 */ 233 escapeSelector: function(selector) { 234 return selector.replace(/[!"$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, '\\$&'); 235 }, 236 237 displaySidebar: function() { 238 if (!this.isDesktop() || this.isPisces() || this.isGemini()) { 239 return; 240 } 241 $('.sidebar-toggle').trigger('click'); 242 }, 243 244 isMist: function() { 245 return CONFIG.scheme === 'Mist'; 246 }, 247 248 isPisces: function() { 249 return CONFIG.scheme === 'Pisces'; 250 }, 251 252 isGemini: function() { 253 return CONFIG.scheme === 'Gemini'; 254 }, 255 256 getScrollbarWidth: function() { 257 var $div = $('<div />').addClass('scrollbar-measure').prependTo('body'); 258 var div = $div[0]; 259 var scrollbarWidth = div.offsetWidth - div.clientWidth; 260 261 $div.remove(); 262 263 return scrollbarWidth; 264 }, 265 266 getContentVisibilityHeight: function() { 267 var docHeight = $('#content').height(); 268 var winHeight = $(window).height(); 269 var contentVisibilityHeight = docHeight > winHeight ? docHeight - winHeight : $(document).height() - winHeight; 270 return contentVisibilityHeight; 271 }, 272 273 getSidebarb2tHeight: function() { 274 //var sidebarb2tHeight = (CONFIG.sidebar.b2t) ? document.getElementsByClassName('back-to-top')[0].clientHeight : 0; 275 var sidebarb2tHeight = CONFIG.sidebar.b2t ? $('.back-to-top').height() : 0; 276 return sidebarb2tHeight; 277 }, 278 279 getSidebarSchemePadding: function() { 280 var sidebarNavHeight = $('.sidebar-nav').css('display') === 'block' ? $('.sidebar-nav').outerHeight(true) : 0; 281 var sidebarInner = $('.sidebar-inner'); 282 var sidebarPadding = sidebarInner.innerWidth() - sidebarInner.width(); 283 var sidebarOffset = CONFIG.sidebar.offset ? CONFIG.sidebar.offset : 12; 284 var sidebarSchemePadding = this.isPisces() || this.isGemini() 285 ? (sidebarPadding * 2) + sidebarNavHeight + sidebarOffset + this.get
285Sidebarb2tHeight() 286 : (sidebarPadding * 2) + (sidebarNavHeight / 2); 287 return sidebarSchemePadding; 288 } 289 290}; 291 292$(document).ready(function() { 293 294 /** 295 * Init Sidebar & TOC inner dimensions on all pages and for all schemes. 296 * Need for Sidebar/TOC inner scrolling if content taller then viewport. 297 */ 298 299 function updateSidebarHeight(height) { 300 height = height || 'auto'; 301 $('.site-overview, .post-toc').css('max-height', height); 302 } 303 304 function initSidebarDimension() { 305 var updateSidebarHeightTimer; 306 307 $(window).on('resize', function() { 308 updateSidebarHeightTimer && clearTimeout(updateSidebarHeightTimer); 309 310 updateSidebarHeightTimer = setTimeout(function() { 311 var sidebarWrapperHeight = document.body.clientHeight - NexT.utils.getSidebarSchemePadding(); 312 313 updateSidebarHeight(sidebarWrapperHeight); 314 }, 0); 315 }); 316 317 // Initialize Sidebar & TOC Width. 318 var scrollbarWidth = NexT.utils.getScrollbarWidth(); 319 if ($('.site-overview-wrap').height() > (document.body.clientHeight - NexT.utils.getSidebarSchemePadding())) { 320 $('.site-overview').css('width', 'calc(100% + ' + scrollbarWidth + 'px)'); 321 } 322 if ($('.post-toc-wrap').height() > (document.body.clientHeight - NexT.utils.getSidebarSchemePadding())) { 323 $('.post-toc').css('width', 'calc(100% + ' + scrollbarWidth + 'px)'); 324 } 325 326 // Initialize Sidebar & TOC Height. 327 updateSidebarHeight(document.body.clientHeight - NexT.utils.getSidebarSchemePadding()); 328 } 329 330 initSidebarDimension(); 331 332});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.