PageSourceSearch

https://northword.github.io/js/src/utils.js?v=6.5.0

js northword.github.io collected 2026-10-03 09:41:46 UTC 10,503 bytes, 332 lines download raw bytes

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.