PageSourceSearch

https://www.drgeek.fr/wp-content/plugins/minervakb/assets/js/minerva-kb.js?ver=1.4.6

js drgeek.fr collected 2026-10-06 13:14:25 UTC 39,255 bytes, 1,218 lines download raw bytes

1/**
2 * Project: Minerva KB
3 * Copyright: 2015-2016 @KonstruktStudio
4 */
5(function ($) {
6
7    var GLOBAL_DATA = window.MinervaKB;
8
9    var i18n = GLOBAL_DATA.i18n;
10    var platform = GLOBAL_DATA.platform;
11    var settings = GLOBAL_DATA.settings;
12    var info = GLOBAL_DATA.info;
13
14    /**
15     * libs
16     */
17    if (!String.prototype.trim) {
18        String.prototype.trim = function () {
19            return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
20        };
21    }
22
23    /**
24     * Debounces function execution
25     * TODO: make shared utils lib
26     * @param func
27     * @param wait
28     * @param immediate
29     * @returns {Function}
30     */
31    function debounce(func, wait, immediate) {
32        var timeout;
33        return function () {
34            var context = this, args = arguments;
35            var later = function () {
36                timeout = null;
37                if (!immediate) {
38                    func.apply(context, args);
39                }
40            };
41            var callNow = immediate && !timeout;
42            clearTimeout(timeout);
43            timeout = setTimeout(later, wait);
44            if (callNow) {
45                func.apply(context, args);
46            }
47        };
48    }
49
50    /**
51     * Throttles function execution. Based on Ben Alman implementation
52     * TODO: make shared utils lib
53     * @param delay
54     * @param noTrailing
55     * @param callback
56     * @param atBegin
57     * @returns {wrapper}
58     */
59    function throttle(delay, noTrailing, callback, atBegin) {
60        var timeoutId;
61        var lastExec = 0;
62
63        if (typeof noTrailing !== 'boolean') {
64            atBegin = callback;
65            callback = noTrailing;
66            noTrailing = undefined;
67        }
68
69        function wrapper() {
70            var elapsed = +new Date() - lastExec;
71            var args = arguments;
72
73            var exec = function _exec() {
74                lastExec = +new Date();
75                callback.apply(this, args );
76            }.bind(this);
77
78            function clear() {
79                timeoutId = undefined;
80            }
81
82            if (atBegin && !timeoutId) {
83                exec();
84            }
85
86            timeoutId && clearTimeout(timeoutId);
87
88            if (atBegin === undefined && elapsed > delay) {
89                exec();
90            } else if (noTrailing !== true) {
91                timeoutId = setTimeout(
92                    atBegin ?
93                        clear :
94                        exec,
95                    atBegin === undefined ?
96                    delay - elapsed :
97                        delay
98                );
99            }
100        }
101
102        return wrapper;
103    }
104
105    function addAjaxNonce(data) {
106        data['nonce_key'] = GLOBAL_DATA.nonce.nonceKey;
107        data['nonce_value'] = GLOBAL_DATA.nonce.nonce;
108
109        return data;
110    }
111
112    /**
113     * Sends Google Analytics event, if API available
114     * @param category
115     * @param action
116     * @param label
117     * @param value
118     */
119    function trackGoogleAnalytics(category, action, label, value) {
120        if (window.ga && typeof window.ga === 'function') {
121            ga('send', 'event', category, action, label, value, {
122                nonInteraction: true
123            });
124        }
125    }
126
127    // theme
128    var ajaxUrl = GLOBAL_DATA.ajaxUrl;
129    var $kbSearch = $('.kb-search__input');
130    var NO_RESULTS_CLASS = 'kb-search__input-wrap--no-results';
131    var HAS_CONTENT_CLASS = 'kb-search__input-wrap--has-content';
132    var HAS_RESULTS_CLASS = 'kb-search__input-wrap--has-results';
133    var REQUEST_CLASS = 'kb-search__input-wrap--request';
134    var hasResults = false;
135    var resultsCount = 0;
136    var activeResult = -1;
137    var ESC = 27;
138    var ENTER = 13;
139    var ARROW_LEFT = 37;
140    var ARROW_UP = 38;
141    var ARROW_RIGHT = 39;
142    var ARROW_DOWN = 40;
143    var $doc = $('html, body');
144    var $adminBar = $('#wpadminbar');
145    var adminOffset = $adminBar.length ? $adminBar.height() : 0;
146    var searchMode = settings['search_mode'];
147    var searchRequestsCount = 0;
148    var searchCache = {};
149    var trackingCache = {};
150
151    /**
152     * Live search result handler
153     * @param $search
154     * @param response
155     */
156    function handleSearchResultsReceive($search, response) {
157        var $wrap = $search.parents('.kb-search__input-wrap');
158        var $summary = $wrap.find('.kb-summary-text-holder');
159        var $results = $wrap.find('.kb-search__results');
160        var results = response.result;
161        var searchNeedle = response.search;
162        var resultsContent;
163        var searchShowTopics = $search.data('show-results-topic') === 1;
164        var showTopicsLabel = $search.data('topic-label');
165
166        if (searchMode === 'nonblocking') {
167            var needle = $search.val() && $search.val().trim();
168
169            if (!needle || needle.length < 3) {
170                // in nonblocking mode user could have already removed the typed string
171
172                results = [];
173                hasResults = false;
174                resultsCount = 0;
175                activeResult = -1;
176                $wrap.removeClass(HAS_RESULTS_CLASS).removeClass(NO_RESULTS_CLASS);
177                $summary.html('');
178                $results.html('');
179
180                return;
181            }
182        }
183
184        if (results && results.length) {
185            if (settings['track_search_with_results']) {
186                trackGoogleAnalytics(
187                    settings['ga_good_search_category'],
188                    settings['ga_good_search_action'],
189                    searchNeedle,
190                    settings['ga_good_search_value'] || 0
191                );
192            }
193
194            hasResults = true;
195            resultsCount = results.length;
196            activeResult = -1;
197            $wrap.removeClass(NO_RESULTS_CLASS).addClass(HAS_RESULTS_CLASS);
198            $summary.html(results.length + ' ' + (results.length === 1 ? i18n['result'] : i18n['results']));
199            resultsContent = results.reduce(function ($el, result) {
200                return $el.append(
201                    '<li>' +
202                        '<a href="' + result.link + '">' +
203                            '<span class="kb-search__result-title">' +
204                            result.title +
205                            '</span>' +
206                            (searchShowTopics && result.topics[0] ?
207                                '<span class="kb-search__result-topic">' +
208                                    '<span class="kb-search__result-topic-label">' + showTopicsLabel + '</span>' +
209                                (result.topics[0]) +
210                                '</span>' :
211                                '') +
212                        '</a>' +
213                    '</li>'
214                );
215            }, $('<ul></ul>'));
216            $results.html(resultsContent);
217        } else {
218            if (settings['track_search_without_results']) {
219                trackGoogleAnalytics(
220                    settings['ga_bad_search_category'],
221                    settings['ga_bad_search_action'],
222                    searchNeedle,
223                    settings['ga_bad_search_value'] || 0
224                );
225            }
226
227            hasResults = false;
228            resultsCount = 0;
229            activeResult = -1;
230            $wrap.removeClass(HAS_RESULTS_CLASS).addClass(NO_RESULTS_CLASS);
231            $summary.html(i18n['no-results']);
232            $results.html('');
233        }
234    }
235
236    function focusInput() {
237        $kbSearch.filter('[data-autofocus="1"]').focus();
238    }
239
240    function nextSearchResult() {
241        var $resultItems = $('.kb-search__results li a');
242
243        activeResult = activeResult + 1 >= resultsCount ? 0 : activeResult + 1;
244        $resultItems.eq(activeResult).focus();
245    }
246
247    function prevSearchResult() {
248        var $resultItems = $('.kb-search__results li a');
249
250        activeResult = activeResult - 1 < 0 ? resultsCount - 1 : activeResult - 1;
251        $resultItems.eq(activeResult).focus();
252    }
253
254    /**
255     * Live search keypress handler
256     * @param e
257     */
258    function onSearchKeyPress(e) {
259
260        if (!$(".kb-search__input").is(":focus") && !$(".kb-search__results a").is(":focus")) {
261            return; //we do not to mess with keypress unless search is in focus
262        }
263
264        var $search = $(".kb-search__input:focus");
265
266        switch (e.keyCode) {
267            case ESC:
268                focusInput();
269                break;
270
271            case ARROW_UP:
272                prevSearchResult();
273                break;
274
275            case ARROW_DOWN:
276                nextSearchResult();
277                break;
278
279            case ENTER:
280                if ($search.length && !$search.val().trim()) {
281                    e.preventDefault();
282                    e.stopImmediatePropagation();
283                }
284                return;
285
286            default:
287                return;
288        }
289
290        e.preventDefault(); // prevent the default action (scroll / move caret)
291    }
292
293    function serializeRequestParams(params) {
294        var serialized = "";
295
296        for (var key in params) {
297            if (serialized !== "") {
298                serialized += "&";
299            }
300
301            serialized += key + "=" + encodeURIComponent(params[key]);
302        }
303
304        return serialized;
305    }
306
307
308
309    /**
310     * Search request stats is saved separately for non-blocking search
311     */
312    function trackSearchRequest(searchParams) {
313
314        searchParams.trackResults = true;
315
316        return $.ajax({
317            method: 'POST',
318            url: ajaxUrl,
319            dataType: 'json',
320            data: searchParams
321        });
322    }
323
324    /**
325     * Waits for a timeout untill tracking results
326     * @param searchParams
327     */
328    var trackingTimerId = null;
329
330    function handleNonBlockingTracking(searchParams) {
331        var serializedParams = serializeRequestParams(searchParams);
332
333        if (trackingTimerId) {
334            clearTimeout(trackingTimerId);
335        }
336
337        if (trackingCache[serializedParams]) {
338            return;
339        }
340
341        trackingTimerId = setTimeout(function() {
342            trackSearchRequest(searchParams).then(function() {
343                trackingCache[serializedParams] = true;
344            });
345        }, 1000);
346    }
347
348    /**
349     * Lice search type handler
350     * @param $search
351     */
352    function onSearchType($search) {
353        var $wrap = $search.parents('.kb-search__input-wrap');
354        var needle = $search.val() && $search.val().trim();
355        var topics = $search.data('topic-ids');
356        var searchParams = {
357            action: 'mkb_kb_search',
358            search: needle,
359            mode: searchMode
360        };
361
362        if (topics) {
363            searchParams.topics = topics;
364        }
365
366        var serializedParams = serializeRequestParams(searchParams);
367
368        if (needle) {
369            $wrap.addClass(HAS_CONTENT_CLASS);
370        } else {
371            $wrap.removeClass(HAS_CONTENT_CLASS);
372        }
373
374        // check cache for response
375        if (settings['search_request_fe_cache'] && searchCache[serializedParams]) {
376            // track cached result, if it wasn tracked before
377            if (searchMode === 'nonblocking') {
378                handleNonBlockingTracking(searchParams);
379            }
380
381            return handleSearchResultsReceive.call(this, $search, searchCache[serializedParams]);
382        }
383
384        if (!needle || needle.length < 3) {
385            hasResults = false;
386            resultsCount = 0;
387            activeResult = -1;
388            $wrap.removeClass(HAS_RESULTS_CLASS).removeClass(NO_RESULTS_CLASS);
389
390            if (searchMode === 'nonblocking' && needle.length > 0) {
391                fakeRequest($wrap); // progress indicator for input to be more responsive
392            }
393
394            return;
395        }
396
397        if (searchMode === 'nonblocking') {
398            handleNonBlockingTracking(searchParams);
399        }
400
401        if (searchMode === 'blocking') {
402            $search.attr('disabled', 'disabled');
403        }
404
405        $wrap.addClass(REQUEST_CLASS);
406        ++searchRequestsCount;
407
408        $.ajax({
409            method: 'GET',
410            url: ajaxUrl,
411            dataType: 'json',
412            data: searchParams
413        })
414            .then(function(response) {
415                if (settings['search_request_fe_cache']) {
416                    searchCache[serializedParams] = response;
417                }
418
419                return handleSearchResultsReceive.call(this, $search, response);
420            }.bind(this))
421            .always(function () {
422                if (searchMode === 'blocking') {
423                    $search
424                        .attr('disabled', false)
425                        .focus();
426                }
427
428                --searchRequestsCount;
429
430                if (searchRequestsCount === 0) {
431                    $wrap.removeClass(REQUEST_CLASS);
432                }
433            });
434    }
435
436    /**
437     * Progress indicator for short requests
438     * @param $wrap
439     */
440    function fakeRequest($wrap) {
441        $wrap.addClass(REQUEST_CLASS);
442        ++searchRequestsCount;
443
444        setTimeout(function() {
445            --searchRequestsCount;
446            if (searchRequestsCount === 0) {
447                $wrap.removeClass(REQUEST_CLASS);
448            }
449        }, 500);
450    }
451
452    /**
453     * Article pageview tracking
454     */
455    function trackArticleView() {
456        var $tracking_meta = $('.mkb-article-extra__tracking-data');
457
458        if (!$tracking_meta.length) {
459            return;
460        }
461
462        var $id = $tracking_meta.data('article-id');
463
464        if (!$id) {
465            return;
466        }
467
468        jQuery.ajax({
469            method: 'POST',
470            url: ajaxUrl,
471            dataType: 'json',
472            data: addAjaxNonce({
473                action: 'mkb_article_pageview',
474                id: $id
475            })
476        });
477    }
478
479    /**
480     * Article like
481     * @param e
482     */
483    function handleArticleLike(e) {
484        e.preventDefault();
485
486        var $likeBtn = $(e.currentTarget);
487        var id = $likeBtn.data('article-id');
488        var title = $likeBtn.data('article-title');
489        var $count = $('.mkb-article-extra__stats-likes');
490        var likes = parseInt($count.text(), 10);
491
492        if (!id || $likeBtn.hasClass('mkb-voted') || $likeBtn.hasClass('mkb-disabled')) {
493            return;
494        }
495
496        $likeBtn.addClass('mkb-voted');
497        $('.mkb-article-extra__dislike').addClass('mkb-disabled');
498        $count.text(++likes);
499
500        jQuery.ajax({
501            method: 'POST',
502            url: ajaxUrl,
503            dataType: 'json',
504            data: addAjaxNonce({
505                action: 'mkb_article_like',
506                id: id
507            })
508        }).done(function() {
509            if (settings['track_article_likes']) {
510                trackGoogleAnalytics(
511                    settings['ga_like_category'],
512                    settings['ga_like_action'],
513                    settings['ga_like_label'] === 'article_title' ? title : id,
514                    settings['ga_like_value'] || 0
515                );
516            }
517
518            if (settings['show_like_message']) {
519                $('.fn-rating-likes-block')
520                    .html($('<div class="mkb-article-extra__message">' + i18n['like_message_text'] + '</div>'));
521            }
522
523            if (settings['enable_feedback'] &&
524                (settings['feedback_mode'] === 'like' || settings['feedback_mode'] === 'any')) {
525                addFeedbackForm();
526            }
527        });
528    }
529
530    /**
531     * Article dislike
532     * @param e
533     */
534    function handleArticleDislike(e) {
535        e.preventDefault();
536
537        var $dislikeBtn = $(e.currentTarget);
538        var id = $dislikeBtn.data('article-id');
539        var title = $dislikeBtn.data('article-title');
540        var $count = $('.mkb-article-extra__stats-dislikes');
541        var dislikes = parseInt($count.text(), 10);
542
543        if (!id || $dislikeBtn.hasClass('mkb-voted') || $dislikeBtn.hasClass('mkb-disabled')) {
544            return;
545        }
546
547        $dislikeBtn.addClass('mkb-voted');
548        $('.mkb-article-extra__like').addClass('mkb-disabled');
549        $count.text(++dislikes);
550
551        jQuery.ajax({
552            method: 'POST',
553            url: ajaxUrl,
554            dataType: 'json',
555            data: addAjaxNonce({
556                action: 'mkb_article_dislike',
557                id: id
558            })
559        }).done(function() {
560            if (settings['track_article_dislikes']) {
561                trackGoogleAnalytics(
562                    settings['ga_dislike_category'],
563                    settings['ga_dislike_action'],
564                    settings['ga_dislike_label'] === 'article_title' ? title : id,
565                    settings['ga_dislike_value'] || 0
566                );
567            }
568
569            if (settings['show_dislike_message']) {
570                $('.fn-rating-likes-block')
571                    .html($('<div class="mkb-article-extra__message">' + i18n['dislike_message_text'] + '</div>'));
572            }
573
574            if (settings['enable_feedback'] &&
575                (settings['feedback_mode'] === 'dislike' || settings['feedback_mode'] === 'any')) {
576                addFeedbackForm();
577            }
578        });
579    }
580
581    /**
582     * Renders feedback form if configured
583     */
584    function addFeedbackForm() {
585        $('.fn-article-feedback-container').append($(
586            '<div class="mkb-article-extra__feedback-form mkb-article-extra__feedback-form--no-content fn-feedback-form">' +
587                '<div class="mkb-article-extra__feedback-form-title">' +
588                i18n['feedback_label'] +
589                '</div>' +
590                '<div class="mkb-article-extra__feedback-form-message">' +
591                '<textarea class="mkb-article-extra__feedback-form-message-area" rows="5"></textarea>' +
592                (i18n['feedback_info_text'] ?
593                    ('<div class="mkb-article-extra__feedback-info">' + i18n['feedback_info_text'] + '</div>') :
594                    '') +
595                '</div>' +
596                '<div class="mkb-article-extra__feedback-form-submit">' +
597                '<a href="#">' + i18n['feedback_submit_label'] + '</a>' +
598                '</div>' +
599            '</div>'
600        ));
601    }
602
603    /**
604     * Sends article feedback to server
605     * @param e
606     */
607    function handleFeedbackSubmit(e) {
608        var $trackingMeta = $('.mkb-article-extra__tracking-data');
609
610        e.preventDefault();
611
612        if (!$trackingMeta.length) {
613            return;
614        }
615
616        var id = $trackingMeta.data('article-id');
617        var title = $trackingMeta.data('article-title');
618        var $btn = $(e.target);
619        var $content = $('.mkb-article-extra__feedback-form-message-area');
620
621        if (!id || !$content.val()) {
622            return;
623        }
624
625        $btn
626            .text(i18n['feedback_submit_request_label'])
627            .attr('disabled', 'disabled');
628
629        jQuery.ajax({
630            method: 'POST',
631            url: ajaxUrl,
632            dataType: 'json',
633            data: addAjaxNonce({
634                action: 'mkb_article_feedback',
635                id: id,
636                content: $content.val()
637            })
638        }).done(function() {
639            if (settings['track_article_feedback']) {
640                trackGoogleAnalytics(
641                    settings['ga_feedback_category'],
642                    settings['ga_feedback_action'],
643                    settings['ga_feedback_label'] === 'article_title' ? title : id,
644                    settings['ga_feedback_value'] || 0
645                );
646            }
647
648            $('.fn-article-feedback-container').html(
649                '<div class="mkb-article-extra__feedback-sent-message">' +
650                i18n['feedback_sent_text'] +
651                '</div>'
652            );
653        });
654    }
655
656    /**
657     * Toggle submit available
658     * @param e
659     */
660    function handleFeedbackType(e) {
661        $('.fn-feedback-form').toggleClass('mkb-article-extra__feedback-form--no-content', Boolean($(e.currentTarget).val() < 1));
662    }
663
664    /**
665     * Back to top in articles
666     */
667    function handleArticleBackToTop() {
668        var $container = $('.mkb-container');
669
670        $container.on('click', '.mkb-back-to-top', function (e) {
671            e.preventDefault();
672
673            $doc.animate({
674                scrollTop: settings['back_to_site_top'] ? 0 : $container.offset().top - adminOffset
675            }, 300);
676
677            window.location.hash = '';
678        });
679    }
680
681    /**
682     * Article Table of Contents
683     */
684    function handleArticleTOC() {
685        var $entryContent = $('.mkb-article-text');
686        var $tocList = $('.mkb-anchors-list');
687        var scrollOffset = parseInt(settings['toc_scroll_offset']['size']);
688        var $headings;
689        var isScrollSpy = settings['scrollspy_switch'] && settings['toc_in_content_disable'] &&
690            platform === 'desktop' &&
691            window.outerWidth >= parseInt(settings['article_sidebar_sticky_min_width']['size'], 10);
692
693        // dynamic TOC
694        if ($tocList.hasClass('mkb-anchors-list--dynamic')) {
695            $headings = $entryContent.find('h1, h2, h3, h4, h5, h6');
696
697            if (settings['show_back_to_top']) {
698                wrapTOCAnchors($headings);
699            }
700
701        } else { // via shortcodes
702            $headings = $('.mkb-anchor__title');
703        }
704
705        // scroll
706        var headingOffsets = Array.prototype.map.call($headings, function(heading) {
707            return $(heading).offset().top;
708        });
709        var $links = $tocList.find('.mkb-anchors-list__item-link');
710        var linksArr = Array.prototype.map.call($links, function(link) {
711            return link;
712        }).filter(function(link) {
713            return link.dataset.index !== '-1'; // other pages links
714        });
715
716        function recalculateMetrics() {
717            headingOffsets = Array.prototype.map.call($headings, function(heading) {
718                return $(heading).offset().top;
719            });
720        }
721
722        $(window).on('load', recalculateMetrics);
723        $(window).on('resize', debounce(recalculateMetrics, 1000));
724
725        setTimeout(recalculateMetrics, 3000); // in case load fails
726
727        var navScrolling = false;
728
729        function navigateToChapter(index) {
730            navScrolling = true;
731
732            if (settings['toc_url_update']) { // TODO: replace with browserHistory instead
733                window.location.hash = 'ch_' + (index + 1);
734            }
735
736            $doc.animate({
737                scrollTop: headingOffsets[index] - scrollOffset - 10
738            }, 300, 'swing', function() {
739
740
741                if (isScrollSpy && linksArr) {
742                    $(linksArr).removeClass('active');
743                    $(linksArr[index]).addClass('active');
744                }
745
746                setTimeout(function() {
747                    navScrolling = false;
748                }, 300);
749            });
750        }
751
752        // link click handlers
753        $tocList.on('click', '.mkb-anchors-list__item-link', function (e) {
754            var $item = $(e.currentTarget);
755
756            if ($item.attr('href') !== '#') {
757                return; // normal link, just navigate
758            }
759
760            var index = parseInt($item.data('index'), 10);
761
762            e.preventDefault();
763
764            if (isScrollSpy) {
765                $links.removeClass('active');
766                $item.addClass('active');
767            }
768
769            navigateToChapter(index);
770        });
771
772        var chapterHash = window.location.hash && window.location.hash.replace(/^#/, '');
773
774        if (chapterHash && chapterHash.indexOf('ch_') !== -1) {
775            var chapterIndex = parseInt(chapterHash.replace(/^ch_/, '')) - 1;
776
777            if (chapterIndex && chapterIndex > 0) {
778                setTimeout(function() {
779                    navigateToChapter(chapterIndex);
780                }, 300);
781            } else if (isScrollSpy && chapterIndex === 0) {
782                $(linksArr[0]).addClass('active');
783            }
784        } else if (isScrollSpy) {
785            $(linksArr[0]).addClass('active');
786        }
787
788        if (!isScrollSpy) {
789            return;
790        }
791
792        // ScrollSpy
793        var win = window;
794        var doc = document.documentElement;
795
796        $(win).on('scroll', throttle(150, function() {
797            if (navScrolling) { return; }
798
799            var top = win.pageYOffset || doc.scrollTop;
800
801            linksArr.forEach(function(item, index) {
802                var curr = index === 0 ? 0 : headingOffsets[index] - scrollOffset - 1;
803                var next = (index === headingOffsets.length - 1 ? 9999999 : headingOffsets[index + 1]) - scrollOffset - 1;
804
805                $(item).toggleClass('active', top >= curr && top < next);
806            });
807        }));
808    }
809
810    /**
811     * Wraps headings n back to top containers when necessary
812     * @param $headings
813     */
814    function wrapTOCAnchors($headings) {
815        $headings.each(function(index, el) {
816            var $heading = $(el);
817
818            $heading.wrap('<div class="mkb-anchor mkb-clearfix mkb-back-to-top-' +
819            settings['back_to_top_position'] +
820            '"></div>');
821
822            $heading.addClass('mkb-anchor__title');
823
824            $heading.parent().append('<a href="#" class="mkb-back-to-top" title="' +
825            settings['back_to_top_text'] + '">' +
826            settings['back_to_top_text'] +
827            (
828                settings['show_back_to_top_icon'] ?
829                '<i class="mkb-back-to-top-icon fa ' + settings['back_to_top_icon'] + '"></i>' :
830                    ''
831            ) +
832            '</a>');
833        });
834    }
835
836    /**
837     * Articles fancy box
838     */
839    function initArticlesFancyBox() {
840        if (!$.fn.fancybox) {
841            return;
842        }
843
844        // with captions
845        $('figure[id^="attachment"] a').each(function (index, item) {
846            var $item = $(item);
847            var text = $item.parent().find('.wp-caption-text').text();
848
849            $item.fancybox({
850                titlePosition: 'over',
851                title: text
852            });
853        });
854
855        // no captions
856        $('.mkb-single-content img').each(function(i, img) {
857            var $img = $(img);
858            var $link = $img.parent();
859
860            if ($img.parents('figure.wp-caption').length || !$link.attr('href')) {
861                return;
862            }
863
864            $link.fancybox({
865                titlePosition: 'none',
866                title: ''
867            });
868        });
869    }
870
871    /**
872     * Search clear
873     * @param e
874     */
875    function handleSearchClear(e) {
876        e.preventDefault();
877
878        $(e.currentTarget)
879            .parents('.kb-search__input-wrap')
880            .find('.kb-search__input')
881            .val('')
882            .trigger('input')
883            .focus();
884    }
885
886    function initSearchInputs() {
887        $kbSearch.each(function (index, el) {
888            var $search = $(el);
889
890            var searchHandler = searchMode === 'blocking' ?
891                debounce(onSearchType.bind(this, $search), parseInt(settings['search_delay'], 10) || 1000, false) :
892                throttle(parseInt(settings['search_delay'], 10) || 300, true, onSearchType.bind(this, $search), true);
893
894            $search.on('input', searchHandler);
895        });
896    }
897
898    /**
899     * Detects if live search disabled for current platform
900     * @returns {boolean}
901     */
902    function isSearchDisabled() {
903        return Boolean(settings['live_search_disable_' + platform]);
904    }
905
906    /**
907     * Sticky sidebar
908     */
909    function setupArticleStickySidebar() {
910
911        if (settings['single_template'] === 'theme' ||
912            !settings['article_sidebar_sticky'] ||
913            !info.isSingle ||
914            platform !== 'desktop' ||
915            window.outerWidth < parseInt(settings['article_sidebar_sticky_min_width']['size'], 10)) {
916
917            // sticky sidebar not enabled or not allowed
918            return;
919        }
920
921        var sticky = false;
922        var atBottom = false;
923        var $sidebar = $('.mkb-sidebar');
924        var sidebarHeight = $sidebar.outerHeight();
925        var $root = $sidebar.parents('.mkb-root');
926        var rootHeight = $root.outerHeight();
927        var rootHeightInner = $root.height();
928        var rootPad = rootHeight - rootHeightInner;
929        var rootTop = $root.offset().top;
930        var triggerPos = rootTop - rootPad / 2;
931        var winHeight = window.innerHeight;
932        var bottomOffset = winHeight > sidebarHeight + rootPad ? winHeight - sidebarHeight - rootPad : 0;
933        var width = $sidebar.outerWidth();
934        var win = window;
935        var doc = document.documentElement;
936
937        function recalculateMetrics() {
938            rootHeight = $root.outerHeight();
939            rootHeightInner = $root.height();
940            rootPad = rootHeight - rootHeightInner;
941            rootTop = $root.offset().top;
942            triggerPos = rootTop - rootPad / 2;
943            winHeight = window.innerHeight;
944            sidebarHeight = $sidebar.outerHeight();
945            bottomOffset = winHeight > sidebarHeight + rootPad ? winHeight - sidebarHeight - rootPad : 0;
946        }
947
948        $(win).on('load', recalculateMetrics);
949        $(win).on('resize', debounce(recalculateMetrics, 1000));
950
951        setTimeout(recalculateMetrics, 3000); // in case load fails
952
953        $(win).on('scroll', function() {
954            var top = win.pageYOffset || doc.scrollTop;
955            var bottom = top + winHeight - bottomOffset;
956
957            if (bottom > rootHeight + rootTop && !atBottom || bottom <= rootHeight + rootTop && atBottom) {
958                atBottom = !atBottom;
959                $sidebar.toggleClass('mkb-fixed-bottom', atBottom);
960            }
961
962            if (sticky && top >= triggerPos || !sticky && top < triggerPos) {
963                return;
964            }
965
966            sticky = !sticky;
967            $sidebar.toggleClass('mkb-fixed', sticky);
968            $sidebar.css('max-width', sticky ? width + 'px' : 'none');
969        });
970    }
971
972    /**
973     * FAQ
974     */
975    function setupFaq() {
976        var $faqContainer = $('.fn-kb-faq-container');
977
978        if (!$faqContainer.length) {
979            return;
980        }
981
982        $faqContainer.each(function(index, item) {
983            var $container = $(item);
984            var FAQ_HIDDEN_CLASS = 'mkb-faq-item-hidden';
985            var FAQ_SECTION_HIDDEN_CLASS = 'mkb-faq-section-hidden';
986            var $filterForm = $container.find('.fn-kb-faq-filter');
987            var $filter = $filterForm.find('.fn-kb-faq-filter-input');
988            var $sections = $container.find('.fn-kb-faq-section');
989            var $noResults = $container.find('.fn-kb-faq-no-results');
990
991            // FAQ sections
992            var sections = [].map.call($sections, function(section) {
993                var $section = $(section);
994                var $count = $section.find('.fn-kb-faq-section-count');
995                var $items = [].map.call($section.find('.fn-kb-faq-item'), function(item) {
996                    var $item = $(item)
997                    return {
998                        $el: $item,
999                        question: $item.find('.fn-kb-faq-question').text().trim().toLowerCase(),
1000                        answer: $item.find('.fn-kb-faq-answer').text().trim().toLowerCase(),
1001                        isVisible: true
1002                    };
1003                })
1004
1005                return {
1006                    $el: $section,
1007                    $countEl: $count.length ? $count : null,
1008                    items: $items,
1009                    visible: [].map.call($items, function(item) { return $(item) }),
1010                    hidden: []
1011                }
1012            });
1013
1014            /**
1015             * FAQ Filter
1016             */
1017            var currentFilter;
1018
1019            function updateFilter(visCheck) {
1020                visCheck = visCheck || checkVisibility
1021
1022                var totalVisible = 0
1023
1024                // check visibility
1025                sections.forEach(function(section) {
1026                    section.visible = []
1027                    section.hidden = []
1028                    section.items.forEach(function(item) {
1029                        section[(item.isVisible = visCheck(item)) ? 'visible' : 'hidden'].push(item)
1030                    })
1031                    totalVisible += section.visible.length
1032                })
1033
1034                $noResults.toggleClass('mkb-hidden', totalVisible > 0)
1035
1036                sections.forEach(function(section) {
1037                    section.visible.forEach(function(item) {
1038                        item.$el.removeClass(FAQ_HIDDEN_CLASS)
1039                    })
1040                    section.hidden.forEach(function(item) {
1041                        item.$el.addClass(FAQ_HIDDEN_CLASS)
1042                    })
1043                    section.$countEl && section.$countEl.html(section.visible.length +
1044                    ' ' + (section.visible.length === 1 ? i18n['question'] : i18n['questions'])
1045                    )
1046                    section.$el.toggleClass(FAQ_SECTION_HIDDEN_CLASS, !section.visible.length)
1047                    section.visible = []
1048                    section.hidden = []
1049                })
1050
1051                if (settings['faq_filter_open_single'] && totalVisible === 1) {
1052                    var $onlyItem = $container.find('.fn-kb-faq-item:not(.' + FAQ_HIDDEN_CLASS + ')')
1053
1054                    if ($onlyItem.length && !$onlyItem.hasClass('kb-faq__questions-list-item--open')) {
1055                        toggleAnswer($onlyItem)
1056                    }
1057                }
1058            }
1059
1060            var resetFilter = updateFilter.bind(this, function() { return true })
1061
1062            function checkVisibility(item) {
1063                return item.question.indexOf(currentFilter) !== -1 || item.answer.indexOf(currentFilter) !== -1
1064            }
1065
1066            function handleFilterChange(e) {
1067                var needle = (e.currentTarget.value || '').trim();
1068
1069                if (needle.length < 3) {
1070                    $filterForm.addClass('kb-faq__filter--empty')
1071                    currentFilter = '';
1072                    resetFilter();
1073
1074                    $filter.focus()
1075                    return
1076                }
1077
1078                $filterForm.removeClass('kb-faq__filter--empty')
1079                currentFilter = needle.toLowerCase();
1080                updateFilter()
1081            }
1082
1083            function handleFilterClear(e) {
1084                e.preventDefault()
1085
1086                $filter.val('').trigger('input')
1087            }
1088
1089            if ($filterForm.length) {
1090                $container.on('input', '.fn-kb-faq-filter-input', handleFilterChange)
1091                $container.on('click', '.fn-kb-faq-filter-clear', handleFilterClear)
1092            }
1093
1094            var OPEN_SPEED = settings['faq_slow_animation'] ? 400 : 100;
1095
1096            function getMaxHeight(el) {
1097                return Array.prototype.reduce.call(el.childNodes, function(store, current) {
1098                    return store + (current.offsetHeight || 0);
1099                }, 0)
1100            }
1101
1102            /**
1103             * FAQ Toggle
1104             */
1105            function toggleAnswer($item) {
1106                var $answer = $item.find('.fn-kb-faq-answer')
1107                var answerEl = $answer.get(0);
1108                var maxHeight = getMaxHeight(answerEl);
1109
1110                if ($item.hasClass('kb-faq__questions-list-item--open')) {
1111                    $answer.css('max-height', maxHeight)
1112                    $answer.animate({maxHeight: 0}, OPEN_SPEED, 'swing', function() {
1113                        $item.removeClass('kb-faq__questions-list-item--open')
1114                    })
1115                } else {
1116                    $answer.animate({maxHeight: maxHeight}, OPEN_SPEED, 'swing', function() {
1117                        $answer.css('max-height', 'none')
1118                        $item.addClass('kb-faq__questions-list-item--open')
1119                    })
1120                }
1121            }
1122
1123            function openAnswer($item) {
1124                var $answer = $item.find('.fn-kb-faq-answer');
1125                var answerEl = $answer.get(0);
1126                var maxHeight = getMaxHeight(answerEl);
1127
1128                if (!$item.hasClass('kb-faq__questions-list-item--open')) {
1129                    $answer.animate({maxHeight: maxHeight}, OPEN_SPEED, 'swing', function() {
1130                        $answer.css('max-height', 'none')
1131                        $item.addClass('kb-faq__questions-list-item--open')
1132                    })
1133                }
1134            }
1135
1136            function closeAnswer($item) {
1137                var $answer = $item.find('.fn-kb-faq-answer');
1138                var answerEl = $answer.get(0);
1139                var maxHeight = getMaxHeight(answerEl);
1140
1141                if ($item.hasClass('kb-faq__questions-list-item--open')) {
1142                    $answer.css('max-height', maxHeight)
1143                    $answer.animate({maxHeight: 0}, OPEN_SPEED, 'swing', function() {
1144                        $item.removeClass('kb-faq__questions-list-item--open')
1145                    })
1146                }
1147            }
1148
1149            function handleQuestionClick (e) {
1150                e.preventDefault();
1151
1152                var $link = $(e.currentTarget)
1153                var $item = $link.parent()
1154
1155                toggleAnswer($item)
1156            }
1157
1158            function handleToggleAllClick (e) {
1159                e.preventDefault()
1160
1161                var $link = $(e.currentTarget)
1162                var isOpen = $link.hasClass('kb-faq__toggle-all-link--open')
1163
1164                sections.forEach(function(section) {
1165                    section.items.forEach(function(item) {
1166                        isOpen ? closeAnswer(item.$el) : openAnswer(item.$el)
1167                    })
1168                })
1169
1170                $link.toggleClass('kb-faq__toggle-all-link--open')
1171            }
1172
1173            $container.on('click', '.fn-kb-faq-link', handleQuestionClick)
1174            $container.on('click', '.fn-kb-faq-toggle-all', handleToggleAllClick)
1175        });
1176    }
1177
1178    function init() {
1179        var $body = $('body');
1180        $adminBar = $('#wpadminbar');
1181        adminOffset = $adminBar.length ? $adminBar.height() : 0;
1182
1183        if ($kbSearch.length && !isSearchDisabled()) {
1184            initSearchInputs();
1185            $body.on('keydown', onSearchKeyPress);
1186            $body.on('click', '.kb-search__clear', handleSearchClear);
1187            focusInput();
1188            onSearchType($kbSearch.eq(0)); // restore previous search
1189        }
1190
1191        // FAQ items
1192        setupFaq();
1193
1194        // article related code
1195        if (info.isSingle) {
1196            $body.on('click', '.mkb-article-extra__like', handleArticleLike);
1197            $body.on('click', '.mkb-article-extra__dislike', handleArticleDislike);
1198            $body.on('click', '.mkb-article-extra__feedback-form-submit', handleFeedbackSubmit);
1199            $body.on('input', '.mkb-article-extra__feedback-form-message-area', handleFeedbackType);
1200
1201            handleArticleBackToTop();
1202            handleArticleTOC();
1203
1204            if (settings['article_fancybox']) {
1205                initArticlesFancyBox();
1206            }
1207
1208            trackArticleView();
1209
1210            // sticky articles sidebar
1211            setupArticleStickySidebar();
1212        }
1213    }
1214
1215    // start
1216    $(document).ready(init);
1217
1218})(jQuery);

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.