PageSourceSearch

https://premiumcoding.com/wp-content/plugins/minervakb/assets/js/minerva-kb.js

js premiumcoding.com collected 2026-09-24 19:02:29 UTC 53,553 bytes, 1,628 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    var $body = $('body');
15
16    /**
17     * libs
18     */
19    if (!String.prototype.trim) {
20        String.prototype.trim = function () {
21            return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
22        };
23    }
24
25    /**
26     * Debounces function execution
27     * TODO: make shared utils lib
28     * @param func
29     * @param wait
30     * @param immediate
31     * @returns {Function}
32     */
33    function debounce(func, wait, immediate) {
34        var timeout;
35        return function () {
36            var context = this, args = arguments;
37            var later = function () {
38                timeout = null;
39                if (!immediate) {
40                    func.apply(context, args);
41                }
42            };
43            var callNow = immediate && !timeout;
44            clearTimeout(timeout);
45            timeout = setTimeout(later, wait);
46            if (callNow) {
47                func.apply(context, args);
48            }
49        };
50    }
51
52    /**
53     * Throttles function execution. Based on Ben Alman implementation
54     * TODO: make shared utils lib
55     * @param delay
56     * @param noTrailing
57     * @param callback
58     * @param atBegin
59     * @returns {wrapper}
60     */
61    function throttle(delay, noTrailing, callback, atBegin) {
62        var timeoutId;
63        var lastExec = 0;
64
65        if (typeof noTrailing !== 'boolean') {
66            atBegin = callback;
67            callback = noTrailing;
68            noTrailing = undefined;
69        }
70
71        function wrapper() {
72            var elapsed = +new Date() - lastExec;
73            var args = arguments;
74
75            var exec = function _exec() {
76                lastExec = +new Date();
77                callback.apply(this, args );
78            }.bind(this);
79
80            function clear() {
81                timeoutId = undefined;
82            }
83
84            if (atBegin && !timeoutId) {
85                exec();
86            }
87
88            timeoutId && clearTimeout(timeoutId);
89
90            if (atBegin === undefined && elapsed > delay) {
91                exec();
92            } else if (noTrailing !== true) {
93                timeoutId = setTimeout(
94                    atBegin ?
95                        clear :
96                        exec,
97                    atBegin === undefined ?
98                    delay - elapsed :
99                        delay
100                );
101            }
102        }
103
104        return wrapper;
105    }
106
107    function addAjaxNonce(data) {
108        data['nonce_key'] = GLOBAL_DATA.nonce.nonceKey;
109        data['nonce_value'] = GLOBAL_DATA.nonce.nonce;
110
111        return data;
112    }
113
114    /**
115     * Sends Google Analytics event, if API available
116     * @param category
117     * @param action
118     * @param label
119     * @param value
120     */
121    function trackGoogleAnalytics(category, action, label, value) {
122        if (window.ga && typeof window.ga === 'function') {
123            ga('send', 'event', category, action, label, value, {
124                nonInteraction: true
125            });
126        }
127    }
128
129    // theme
130    var ajaxUrl = GLOBAL_DATA.ajaxUrl;
131    var $kbSearch = $('.kb-search__input');
132    var NO_RESULTS_CLASS = 'kb-search__input-wrap--no-results';
133    var HAS_CONTENT_CLASS = 'kb-search__input-wrap--has-content';
134    var HAS_RESULTS_CLASS = 'kb-search__input-wrap--has-results';
135    var REQUEST_CLASS = 'kb-search__input-wrap--request';
136    var hasResults = false;
137    var resultsCount = 0;
138    var activeResult = -1;
139    var ESC = 27;
140    var ENTER = 13;
141    var ARROW_LEFT = 37;
142    var ARROW_UP = 38;
143    var ARROW_RIGHT = 39;
144    var ARROW_DOWN = 40;
145    var $doc = $('html, body');
146    var $adminBar = $('#wpadminbar');
147    var adminOffset = $adminBar.length ? $adminBar.height() : 0;
148    var searchMode = settings['search_mode'];
149    var searchNeedleLength = parseInt(settings['search_needle_length']);
150    var searchRequestsCount = 0;
151    var searchCache = {};
152    var trackingCache = {};
153
154    /**
155     * Live search result handler
156     * @param $search
157     * @param response
158     */
159    function handleSearchResultsReceive($search, response) {
160        var $wrap = $search.parents('.kb-search__input-wrap');
161        var $summary = $wrap.find('.kb-summary-text-holder');
162        var $results = $wrap.find('.kb-search__results');
163        var results = response.result;
164        var searchNeedle = response.search;
165        var resultsContent;
166        var resultsInfoHtml = response.results_info || '';
167        var searchShowTopics = $search.data('show-results-topic') === 1;
168        var showTopicsLabel = $search.data('topic-label');
169        var showExcerpt = settings['live_search_show_excerpt'];
170
171        if (searchMode === 'nonblocking') {
172            var needle = $search.val() && $search.val().trim();
173
174            if (!needle || needle.length < searchNeedleLength) {
175                // in nonblocking mode user could have already removed the typed string
176
177                results = [];
178                hasResults = false;
179                resultsCount = 0;
180                activeResult = -1;
181                $wrap.removeClass(HAS_RESULTS_CLASS).removeClass(NO_RESULTS_CLASS);
182                $summary.html('');
183                $results.html('');
184
185                return;
186            }
187        }
188
189        if (results && results.length) {
190            if (settings['track_search_with_results']) {
191                trackGoogleAnalytics(
192                    settings['ga_good_search_category'],
193                    settings['ga_good_search_action'],
194                    searchNeedle,
195                    settings['ga_good_search_value'] || 0
196                );
197            }
198
199            hasResults = true;
200            resultsCount = results.length;
201            activeResult = -1;
202            $wrap.removeClass(NO_RESULTS_CLASS).addClass(HAS_RESULTS_CLASS);
203            $summary.html(results.length + ' ' + (results.length === 1 ? i18n['result'] : i18n['results']));
204            resultsContent = results.reduce(function ($el, result) {
205                return $el.append(
206                    '<li>' +
207                        '<a href="' + result.link + '">' +
208                            '<span class="kb-search__result-header">' +
209                                '<span class="kb-search__result-title">' +
210                                result.title +
211                                '</span>' +
212                                (searchShowTopics && result.topics[0] ?
213                                    '<span class="kb-search__result-topic">' +
214                                        '<span class="kb-search__result-topic-label">' + showTopicsLabel + '</span>' +
215                                        '<span class="kb-search__result-topic-name">' + (result.topics[0])  + '</span>' +
216                                    '</span>' :
217                                    '') +
218                            '</span>' +
219                            (showExcerpt ? '<span class="kb-search__result-excerpt">' + result.excerpt + ' ...</span>' : '') +
220                        '</a>' +
221                    '</li>'
222                );
223            }, $('<ul></ul>'));
224
225            $results.html('');
226            $results.append(resultsInfoHtml);
227            $results.append(resultsContent);
228        } else {
229            if (settings['track_search_without_results']) {
230                trackGoogleAnalytics(
231                    settings['ga_bad_search_category'],
232                    settings['ga_bad_search_action'],
233                    searchNeedle,
234                    settings['ga_bad_search_value'] || 0
235                );
236            }
237
238            hasResults = false;
239            resultsCount = 0;
240            activeResult = -1;
241            $wrap.removeClass(HAS_RESULTS_CLASS).addClass(NO_RESULTS_CLASS);
242            $summary.html(i18n['no-results']);
243            $results.html('');
244        }
245    }
246
247    function focusInput() {
248        $kbSearch.filter('[data-autofocus="1"]').focus();
249    }
250
251    function nextSearchResult() {
252        var $resultItems = $('.kb-search__results li a');
253
254        activeResult = activeResult + 1 >= resultsCount ? 0 : activeResult + 1;
255        $resultItems.eq(activeResult).focus();
256    }
257
258    function prevSearchResult() {
259        var $resultItems = $('.kb-search__results li a');
260
261        activeResult = activeResult - 1 < 0 ? resultsCount - 1 : activeResult - 1;
262        $resultItems.eq(activeResult).focus();
263    }
264
265    /**
266     * Live search keypress handler
267     * @param e
268     */
269    function onSearchKeyPress(e) {
270
271        if (!$(".kb-search__input").is(":focus") && !$(".kb-search__results a").is(":focus")) {
272            return; //we do not to mess with keypress unless search is in focus
273        }
274
275        var $search = $(".kb-search__input:focus");
276
277        switch (e.keyCode) {
278            case ESC:
279                focusInput();
280                break;
281
282            case ARROW_UP:
283                prevSearchResult();
284                break;
285
286            case ARROW_DOWN:
287                nextSearchResult();
288                break;
289
290            case ENTER:
291                if ($search.length && !$search.val().trim()) {
292                    e.preventDefault();
293                    e.stopImmediatePropagation();
294                }
295                return;
296
297            default:
298                return;
299        }
300
301        e.preventDefault(); // prevent the default action (scroll / move caret)
302    }
303
304    function serializeRequestParams(params) {
305        var serialized = "";
306
307        for (var key in params) {
308            if (serialized !== "") {
309                serialized += "&";
310            }
311
312            serialized += key + "=" + encodeURIComponent(params[key]);
313        }
314
315        return serialized;
316    }
317
318
319
320    /**
321     * Search request stats is saved separately for non-blocking search
322     */
323    function trackSearchRequest(searchParams) {
324
325        searchParams.trackResults = true;
326
327        return $.ajax({
328            method: 'POST',
329            url: ajaxUrl,
330            dataType: 'json',
331            data: searchParams
332        });
333    }
334
335    /**
336     * Waits for a timeout untill tracking results
337     * @param searchParams
338     */
339    var trackingTimerId = null;
340
341    function handleNonBlockingTracking(searchParams) {
342        var serializedParams = serializeRequestParams(searchParams);
343
344        if (trackingTimerId) {
345            clearTimeout(trackingTimerId);
346        }
347
348        if (trackingCache[serializedParams]) {
349            return;
350        }
351
352        trackingTimerId = setTimeout(function() {
353            trackSearchRequest(searchParams).then(function() {
354                trackingCache[serializedParams] = true;
355            });
356        }, 1000);
357    }
358
359    /**
360     * Lice search type handler
361     * @param $search
362     */
363    function onSearchType($search) {
364        var $wrap = $search.parents('.kb-search__input-wrap');
365        var needle = $search.val() && $search.val().trim();
366        var topics = $search.data('topic-ids');
367        var $kbId = $wrap.find('input[name="kb_id"]');
368        var $lang = $wrap.find('input[name="lang"]');
369        var kbId = $kbId.length ? parseInt($kbId.val()) : null;
370        var searchParams = {
371            action: 'mkb_kb_search',
372            search: needle,
373            mode: searchMode
374        };
375
376        if (kbId) {
377            searchParams.kb_id = kbId;
378        }
379
380        if (topics) {
381            searchParams.topics = topics;
382        }
383
384        if ($lang.length && $lang.val()) {
385            searchParams.lang = $lang.val();
386        }
387
388        var serializedParams = serializeRequestParams(searchParams);
389
390        if (needle) {
391            $wrap.addClass(HAS_CONTENT_CLASS);
392        } else {
393            $wrap.removeClass(HAS_CONTENT_CLASS);
394        }
395
396        // check cache for response
397        if (settings['search_request_fe_cache'] && searchCache[serializedParams]) {
398            // track cached result, if it wasn tracked before
399            if (searchMode === 'nonblocking') {
400                handleNonBlockingTracking(searchParams);
401            }
402
403            return handleSearchResultsReceive.call(this, $search, searchCache[serializedParams]);
404        }
405
406        if (!needle || needle.length < searchNeedleLength) {
407            hasResults = false;
408            resultsCount = 0;
409            activeResult = -1;
410            $wrap.removeClass(HAS_RESULTS_CLASS).removeClass(NO_RESULTS_CLASS);
411
412            if (searchMode === 'nonblocking' && needle.length > 0) {
413                fakeRequest($wrap); // progress indicator for input to be more responsive
414            }
415
416            return;
417        }
418
419        if (searchMode === 'nonblocking') {
420            handleNonBlockingTracking(searchParams);
421        }
422
423        if (searchMode === 'blocking') {
424            $search.attr('disabled', 'disabled');
425        }
426
427        $wrap.addClass(REQUEST_CLASS);
428        ++searchRequestsCount;
429
430        $.ajax({
431            method: settings['live_search_use_post'] ? 'POST' : 'GET',
432            url: ajaxUrl,
433            dataType: 'json',
434            data: searchParams
435        })
436            .then(function(response) {
437                if (settings['search_request_fe_cache']) {
438                    searchCache[serializedParams] = response;
439                }
440
441                return handleSearchResultsReceive.call(this, $search, response);
442            }.bind(this))
443            .always(function () {
444                if (searchMode === 'blocking') {
445                    $search
446                        .attr('disabled', false)
447                        .focus();
448                }
449
450                --searchRequestsCount;
451
452                if (searchRequestsCount === 0) {
453                    $wrap.removeClass(REQUEST_CLASS);
454                }
455            });
456    }
457
458    /**
459     * Progress indicator for short requests
460     * @param $wrap
461     */
462    function fakeRequest($wrap) {
463        $wrap.addClass(REQUEST_CLASS);
464        ++searchRequestsCount;
465
466        setTimeout(function() {
467            --searchRequestsCount;
468            if (searchRequestsCount === 0) {
469                $wrap.removeClass(REQUEST_CLASS);
470            }
471        }, 500);
472    }
473
474    /**
475     * Article pageview tracking
476     */
477    function trackArticleView() {
478        var $tracking_meta = $('.mkb-article-extra__tracking-data');
479
480        if (!$tracking_meta.length) {
481            return;
482        }
483
484        var $id = $tracking_meta.data('article-id');
485
486        if (!$id) {
487            return;
488        }
489
490        jQuery.ajax({
491            method: 'POST',
492            url: ajaxUrl,
493            dataType: 'json',
494            data: addAjaxNonce({
495                action: 'mkb_article_pageview',
496                id: $id
497            })
498        });
499    }
500
501    /**
502     * Article like
503     * @param e
504     */
505    function handleArticleLike(e) {
506        e.preventDefault();
507
508        var $likeBtn = $(e.currentTarget);
509        var id = $likeBtn.data('article-id');
510        var title = $likeBtn.data('article-title');
511        var $count = $('.mkb-article-extra__stats-likes');
512        var likes = parseInt($count.text(), 10);
513
514        if (!id || $likeBtn.hasClass('mkb-voted') || $likeBtn.hasClass('mkb-disabled')) {
515            return;
516        }
517
518        $likeBtn.addClass('mkb-voted');
519        $('.mkb-article-extra__dislike').addClass('mkb-disabled');
520        $count.text(++likes);
521
522        jQuery.ajax({
523            method: 'POST',
524            url: ajaxUrl,
525            dataType: 'json',
526            data: addAjaxNonce({
527                action: 'mkb_article_like',
528                id: id
529            })
530        }).done(function() {
531            if (settings['track_article_likes']) {
532                trackGoogleAnalytics(
533                    settings['ga_like_category'],
534                    settings['ga_like_action'],
535                    settings['ga_like_label'] === 'article_title' ? title : id,
536                    settings['ga_like_value'] || 0
537                );
538            }
539
540            if (settings['show_like_message']) {
541                $('.fn-rating-likes-block')
542                    .html($('<div class="mkb-article-extra__message">' + i18n['like_message_text'] + '</div>'));
543            }
544
545            if (settings['enable_feedback'] &&
546                (settings['feedback_mode'] === 'like' || settings['feedback_mode'] === 'any')) {
547                addFeedbackForm();
548            }
549        });
550    }
551
552    /**
553     * Article dislike
554     * @param e
555     */
556    function handleArticleDislike(e) {
557        e.preventDefault();
558
559        var $dislikeBtn = $(e.currentTarget);
560        var id = $dislikeBtn.data('article-id');
561        var title = $dislikeBtn.data('article-title');
562        var $count = $('.mkb-article-extra__stats-dislikes');
563        var dislikes = parseInt($count.text(), 10);
564
565        if (!id || $dislikeBtn.hasClass('mkb-voted') || $dislikeBtn.hasClass('mkb-disabled')) {
566            return;
567        }
568
569        $dislikeBtn.addClass('mkb-voted');
570        $('.mkb-article-extra__like').addClass('mkb-disabled');
571        $count.text(++dislikes);
572
573        jQuery.ajax({
574            method: 'POST',
575            url: ajaxUrl,
576            dataType: 'json',
577            data: addAjaxNonce({
578                action: 'mkb_article_dislike',
579                id: id
580            })
581        }).done(function() {
582            if (settings['track_article_dislikes']) {
583                trackGoogleAnalytics(
584                    settings['ga_dislike_category'],
585                    settings['ga_dislike_action'],
586                    settings['ga_dislike_label'] === 'article_title' ? title : id,
587                    settings['ga_dislike_value'] || 0
588                );
589            }
590
591            if (settings['show_dislike_message']) {
592                $('.fn-rating-likes-block')
593                    .html($('<div class="mkb-article-extra__message">' + i18n['dislike_message_text'] + '</div>'));
594            }
595
596            if (settings['enable_feedback'] &&
597                (settings['feedback_mode'] === 'dislike' || settings['feedback_mode'] === 'any')) {
598                addFeedbackForm();
599            }
600        });
601    }
602
603    /**
604     * Renders feedback form if configured
605     */
606    function addFeedbackForm() {
607        $('.fn-article-feedback-container').append($(
608            '<div class="mkb-article-extra__feedback-form mkb-article-extra__feedback-form--no-content fn-feedback-form">' +
609                '<div class="mkb-article-extra__feedback-form-title">' +
610                i18n['feedback_label'] +
611                '</div>' +
612                '<div class="mkb-article-extra__feedback-form-message">' +
613                '<textarea class="mkb-article-extra__feedback-form-message-area" rows="5"></textarea>' +
614                (i18n['feedback_info_text'] ?
615                    ('<div class="mkb-article-extra__feedback-info">' + i18n['feedback_info_text'] + '</div>') :
616                    '') +
617                '</div>' +
618                '<div class="mkb-article-extra__feedback-form-submit">' +
619                '<a href="#">' + i18n['feedback_submit_label'] + '</a>' +
620                '</div>' +
621            '</div>'
622        ));
623    }
624
625    /**
626     * Sends article feedback to server
627     * @param e
628     */
629    function handleFeedbackSubmit(e) {
630        var $trackingMeta = $('.mkb-article-extra__tracking-data');
631
632        e.preventDefault();
633
634        if (!$trackingMeta.length) {
635            return;
636        }
637
638        var id = $trackingMeta.data('article-id');
639        var title = $trackingMeta.data('article-title');
640        var $btn = $(e.target);
641        var $content = $('.mkb-article-extra__feedback-form-message-area');
642
643        if (!id || !$content.val()) {
644            return;
645        }
646
647        $btn
648            .text(i18n['feedback_submit_request_label'])
649            .attr('disabled', 'disabled');
650
651        jQuery.ajax({
652            method: 'POST',
653            url: ajaxUrl,
654            dataType: 'json',
655            data: addAjaxNonce({
656                action: 'mkb_article_feedback',
657                id: id,
658                content: $content.val()
659            })
660        }).done(function() {
661            if (settings['track_article_feedback']) {
662                trackGoogleAnalytics(
663                    settings['ga_feedback_category'],
664                    settings['ga_feedback_action'],
665                    settings['ga_feedback_label'] === 'article_title' ? title : id,
666                    settings['ga_feedback_value'] || 0
667                );
668            }
669
670            $('.fn-article-feedback-container').html(
671                '<div class="mkb-article-extra__feedback-sent-message">' +
672                i18n['feedback_sent_text'] +
673                '</div>'
674            );
675        });
676    }
677
678    /**
679     * Toggle submit available
680     * @param e
681     */
682    function handleFeedbackType(e) {
683        $('.fn-feedback-form').toggleClass('mkb-article-extra__feedback-form--no-content', Boolean($(e.currentTarget).val() < 1));
684    }
685
686    /**
687     * Back to top in articles
688     */
689    function handleArticleBackToTop() {
690        var $container = $('.mkb-container');
691
692        $container.on('click', '.mkb-back-to-top', function (e) {
693            e.preventDefault();
694
695            $doc.animate({
696                scrollTop: settings['back_to_site_top'] ? 0 : $container.offset().top - adminOffset
697            }, 300);
698
699            window.location.hash = '';
700        });
701    }
702
703    /**
704     * Article Table of Contents
705     */
706    function handleArticleTOC() {
707        var $entryContent = $('.mkb-article-text');
708        var $tocList = $('.mkb-anchors-list');
709        var scrollOffset = parseInt(settings['toc_scroll_offset']['size']);
710        var headingsExclude = settings['toc_headings_exclude'].trim().toLowerCase();
711        var $headings;
712        var isScrollSpy = settings['scrollspy_switch'] && settings['toc_in_content_disable'] &&
713            platform === 'desktop' &&
714            window.outerWidth >= parseInt(settings['article_sidebar_sticky_min_width']['size'], 10);
715
716        // dynamic TOC
717        if ($tocList.hasClass('mkb-anchors-list--dynamic')) {
718            var headingsPool = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
719
720            if (headingsExclude) {
721                var excluded = headingsExclude.split(',').map(function(heading) {
722                    return heading.trim();
723                }).filter(function(heading) {
724                    return Boolean(heading);
725                });
726
727                headingsPool = headingsPool.filter(function(heading) {
728                    return excluded.indexOf(heading) === -1;
729                });
730            }
731
732            $headings = $entryContent.find(headingsPool.join(', '));
733
734            if (settings['show_back_to_top']) {
735                wrapTOCAnchors($headings);
736            }
737
738        } else { // via shortcodes
739            $headings = $('.mkb-anchor__title');
740        }
741
742        // scroll
743        var headingOffsets = Array.prototype.map.call($headings, function(heading) {
744            return $(heading).offset().top;
745        });
746        var $links = $tocList.find('.mkb-anchors-list__item-link');
747        var linksArr = Array.prototype.map.call($links, function(link) {
748            return link;
749        }).filter(function(link) {
750            return link.dataset.index !== '-1'; // other pages links
751        });
752
753        function recalculateMetrics() {
754            headingOffsets = Array.prototype.map.call($headings, function(heading) {
755                return $(heading).offset().top;
756            });
757        }
758
759        $(window).on('load', recalculateMetrics);
760        $(window).on('resize', debounce(recalculateMetrics, 1000));
761
762        setTimeout(recalculateMetrics, 3000); // in case load fails
763
764        var navScrolling = false;
765
766        function navigateToChapter(index) {
767            navScrolling = true;
768
769            recalculateMetrics();
770
771            if (settings['toc_url_update']) { // TODO: replace with browserHistory instead
772                window.location.hash = 'ch_' + (index + 1);
773            }
774
775            $doc.animate({
776                scrollTop: headingOffsets[index] - scrollOffset - 10
777            }, 300, 'swing', function() {
778
779
780                if (isScrollSpy && linksArr) {
781                    $(linksArr).removeClass('active');
782                    $(linksArr[index]).addClass('active');
783                }
784
785                setTimeout(function() {
786                    navScrolling = false;
787                }, 300);
788            });
789        }
790
791        // link click handlers
792        $tocList.on('click', '.mkb-anchors-list__item-link', function (e) {
793            var $item = $(e.currentTarget);
794
795            if ($item.attr('href') !== '#') {
796                return; // normal link, just navigate
797            }
798
799            var index = parseInt($item.data('index'), 10);
800
801            e.preventDefault();
802
803            if (isScrollSpy) {
804                $links.removeClass('active');
805                $item.addClass('active');
806            }
807
808            navigateToChapter(index);
809        });
810
811        var chapterHash = window.location.hash && window.location.hash.replace(/^#/, '');
812
813        if (chapterHash && chapterHash.indexOf('ch_') !== -1) {
814            var chapterIndex = parseInt(chapterHash.replace(/^ch_/, '')) - 1;
815
816            if (chapterIndex && chapterIndex > 0) {
817                setTimeout(function() {
818                    navigateToChapter(chapterIndex);
819                }, 300);
820            } else if (isScrollSpy && chapterIndex === 0) {
821                $(linksArr[0]).addClass('active');
822            }
823        } else if (isScrollSpy) {
824            $(linksArr[0]).addClass('active');
825        }
826
827        if (!isScrollSpy) {
828            return;
829        }
830
831        // ScrollSpy
832        var win = window;
833        var doc = document.documentElement;
834
835        $(win).on('scroll', throttle(150, function() {
836            if (navScrolling) { return; }
837
838            var top = win.pageYOffset || doc.scrollTop;
839
840            linksArr.forEach(function(item, index) {
841                var curr = index === 0 ? 0 : headingOffsets[index] - scrollOffset - 1;
842                var next = (index === headingOffsets.length - 1 ? 9999999 : headingOffsets[index + 1]) - scrollOffset - 1;
843
844                $(item).toggleClass('active', top >= curr && top < next);
845            });
846        }));
847    }
848
849    /**
850     * Wraps headings n back to top containers when necessary
851     * @param $headings
852     */
853    function wrapTOCAnchors($headings) {
854        $headings.each(function(index, el) {
855            var $heading = $(el);
856
857            $heading.wrap('<div class="mkb-anchor mkb-clearfix mkb-back-to-top-' +
858            settings['back_to_top_position'] +
859            '"></div>');
860
861            $heading.addClass('mkb-anchor__title');
862
863            $heading.parent().append('<a href="#" class="mkb-back-to-top" title="' +
864            settings['back_to_top_text'] + '">' +
865            settings['back_to_top_text'] +
866            (
867                settings['show_back_to_top_icon'] ?
868                '<i class="mkb-back-to-top-icon fa ' + settings['back_to_top_icon'] + '"></i>' :
869                    ''
870            ) +
871            '</a>');
872        });
873    }
874
875    /**
876     * Articles fancy box
877     */
878    function initArticlesFancyBox() {
879        if (!$.fn.fancybox) {
880            return;
881        }
882
883        // with captions
884        $('figure[id^="attachment"] a').each(function (index, item) {
885            var $item = $(item);
886            var text = $item.parent().find('.wp-caption-text').text();
887
888            $item.fancybox({
889                titlePosition: 'over',
890                title: text
891            });
892        });
893
894        // no captions
895        $('.mkb-single-content img').each(function(i, img) {
896            var $img = $(img);
897            var $link = $img.parent();
898
899            if ($img.parents('figure.wp-caption').length || !$link.attr('href')) {
900                return;
901            }
902
903            $link.fancybox({
904                titlePosition: 'none',
905                title: ''
906            });
907        });
908    }
909
910    /**
911     * Search clear
912     * @param e
913     */
914    function handleSearchClear(e) {
915        e.preventDefault();
916
917        $(e.currentTarget)
918            .parents('.kb-search__input-wrap')
919            .find('.kb-search__input')
920            .val('')
921            .trigger('input')
922            .focus();
923    }
924
925    function initSearchInputs() {
926        $kbSearch.each(function (index, el) {
927            var $search = $(el);
928
929            var searchHandler = searchMode === 'blocking' ?
930                debounce(onSearchType.bind(this, $search), parseInt(settings['search_delay'], 10) || 1000, false) :
931                throttle(parseInt(settings['search_delay'], 10) || 300, true, onSearchType.bind(this, $search), true);
932
933            $search.on('input', searchHandler);
934        });
935    }
936
937    /**
938     * Detects if live search disabled for current platform
939     * @returns {boolean}
940     */
941    function isSearchDisabled() {
942        return Boolean(settings['live_search_disable_' + platform]);
943    }
944
945    /**
946     * Sticky sidebar
947     */
948    function setupArticleStickySidebar() {
949
950        if (settings['single_template'] === 'theme' ||
951            !settings['article_sidebar_sticky'] ||
952            !info.isSingle ||
953            platform !== 'desktop' ||
954            window.outerWidth < parseInt(settings['article_sidebar_sticky_min_width']['size'], 10)) {
955
956            // sticky sidebar not enabled or not allowed
957            return;
958        }
959
960        var sticky = false;
961        var sidebarPosition = settings['article_sidebar'];
962        var atBottom = false;
963        var $sidebar = $('.mkb-sidebar');
964        var sidebarHeight = $sidebar.outerHeight();
965        var $root = $sidebar.parents('.mkb-root');
966        var rootHeight = $root.outerHeight();
967        var rootHeightInner = $root.height();
968        var rootPad = rootHeight - rootHeightInner;
969        var rootTop = $root.offset().top;
970        var triggerPos = rootTop - rootPad / 2;
971        var winHeight = window.innerHeight;
972        var bottomOffset = winHeight > sidebarHeight + rootPad ? winHeight - sidebarHeight - rootPad : 0;
973        var width = $sidebar.outerWidth();
974        var win = window;
975        var doc = document.documentElement;
976
977        function recalculateMetrics() {
978            rootHeight = $root.outerHeight();
979            rootHeightInner = $root.height();
980            rootPad = rootHeight - rootHeightInner;
981            rootTop = $root.offset().top;
982            triggerPos = rootTop - rootPad / 2;
983            winHeight = window.innerHeight;
984            sidebarHeight = $sidebar.outerHeight();
985            bottomOffset = winHeight > sidebarHeight + rootPad ? winHeight - sidebarHeight - rootPad : 0;
986        }
987
988        $(win).on('load', recalculateMetrics);
989        $(win).on('resize', debounce(recalculateMetrics, 1000));
990
991        setInterval(recalculateMetrics, 500); // sometimes content height changes dynamically
992
993        function updateSidebarLeftPosition() {
994            var left = $root.get(0).getBoundingClientRect().left;
995            $sidebar.css('left', left + parseInt($root.css('padding-left')) + 'px');
996        }
997
998        function handleScroll() {
999            var top = win.pageYOffset || doc.scrollTop;
1000            var bottom = top + winHeight - bottomOffset;
1001
1002            if (bottom > rootHeight + rootTop && !atBottom || bottom <= rootHeight + rootTop && atBottom) {
1003                atBottom = !atBottom;
1004                $sidebar.toggleClass('mkb-fixed-bottom', atBottom);
1005
1006                if (sidebarPosition === 'left') {
1007                    if (atBottom) {
1008                        $sidebar.css('left', '');
1009                    } else {
1010                        updateSidebarLeftPosition();
1011                    }
1012                }
1013            }
1014
1015            if (sticky && top >= triggerPos || !sticky && top < triggerPos) {
1016                return;
1017            }
1018
1019            sticky = !sticky;
1020            $sidebar.toggleClass('mkb-fixed', sticky);
1021            $sidebar.css('max-width', sticky ? width + 'px' : 'none');
1022
1023            if (sidebarPosition === 'left') {
1024                if (sticky) {
1025                    if (!atBottom) {
1026                        updateSidebarLeftPosition();
1027                    }
1028
1029                    $(window).on('resize', updateSidebarLeftPosition);
1030                } else {
1031                    $sidebar.css('left', 0);
1032                    $(window).off('resize', updateSidebarLeftPosition);
1033                }
1034            }
1035        }
1036
1037        $(win).on('scroll', handleScroll);
1038    }
1039
1040    /**
1041     * FAQ
1042     */
1043    function setupFaq() {
1044        var $faqContainer = $('.fn-kb-faq-container');
1045
1046        if (!$faqContainer.length) {
1047            return;
1048        }
1049
1050        function getHashFaqId() {
1051            var currentHash = window.location.hash.replace('#', '');
1052            return /^qa_/.test(currentHash) && currentHash.replace('qa_', '') || false;
1053        }
1054
1055        function setHashFaq(id) {
1056            history.replaceState(null, '',
1057                window.location.origin + window.location.pathname + window.location.search +
1058                '#qa_' + id);
1059        }
1060
1061        function clearHash() {
1062            history.replaceState(null, '',
1063                window.location.origin + window.location.pathname + window.location.search);
1064        }
1065
1066        $faqContainer.each(function(index, item) {
1067            var $container = $(item);
1068            var FAQ_HIDDEN_CLASS = 'mkb-faq-item-hidden';
1069            var FAQ_SECTION_HIDDEN_CLASS = 'mkb-faq-section-hidden';
1070            var $filterForm = $container.find('.fn-kb-faq-filter');
1071            var $filter = $filterForm.find('.fn-kb-faq-filter-input');
1072            var $sections = $container.find('.fn-kb-faq-section');
1073            var $noResults = $container.find('.fn-kb-faq-no-results');
1074
1075            // FAQ sections
1076            var sections = [].map.call($sections, function(section) {
1077                var $section = $(section);
1078                var $count = $section.find('.fn-kb-faq-section-count');
1079                var $items = [].map.call($section.find('.fn-kb-faq-item'), function(item) {
1080                    var $item = $(item);
1081
1082                    return {
1083                        $el: $item,
1084                        question: $item.find('.fn-kb-faq-question').text().trim().toLowerCase(),
1085                        answer: $item.find('.fn-kb-faq-answer').text().trim().toLowerCase(),
1086                        isVisible: true
1087                    };
1088                });
1089
1090                return {
1091                    $el: $section,
1092                    $countEl: $count.length ? $count : null,
1093                    items: $items,
1094                    visible: [].map.call($items, function(item) { return $(item) }),
1095                    hidden: []
1096                };
1097            });
1098
1099            /**
1100             * FAQ Filter
1101             */
1102            var currentFilter;
1103
1104            function updateFilter(visCheck) {
1105                visCheck = visCheck || checkVisibility;
1106
1107                var totalVisible = 0;
1108
1109                // check visibility
1110                sections.forEach(function(section) {
1111                    section.visible = [];
1112                    section.hidden = [];
1113                    section.items.forEach(function(item) {
1114                        section[(item.isVisible = visCheck(item)) ? 'visible' : 'hidden'].push(item);
1115                    });
1116                    totalVisible += section.visible.length;
1117                });
1118
1119                $noResults.toggleClass('mkb-hidden', totalVisible > 0);
1120
1121                sections.forEach(function(section) {
1122                    section.visible.forEach(function(item) {
1123                        item.$el.removeClass(FAQ_HIDDEN_CLASS);
1124                    });
1125                    section.hidden.forEach(function(item) {
1126                        item.$el.addClass(FAQ_HIDDEN_CLASS);
1127                    });
1128                    section.$countEl && section.$countEl.html(section.visible.length +
1129                    ' ' + (section.visible.length === 1 ? i18n['question'] : i18n['questions'])
1130                    );
1131                    section.$el.toggleClass(FAQ_SECTION_HIDDEN_CLASS, !section.visible.length);
1132                    section.visible = [];
1133                    section.hidden = [];
1134                });
1135
1136                if (settings['faq_filter_open_single'] && totalVisible === 1) {
1137                    var $onlyItem = $container.find('.fn-kb-faq-item:not(.' + FAQ_HIDDEN_CLASS + ')');
1138
1139                    if ($onlyItem.length && !$onlyItem.hasClass('kb-faq__questions-list-item--open')) {
1140                        toggleAnswer($onlyItem);
1141                    }
1142                }
1143            }
1144
1145            var resetFilter = updateFilter.bind(this, function() { return true; });
1146
1147            function checkVisibility(item) {
1148                return item.question.indexOf(currentFilter) !== -1 || item.answer.indexOf(currentFilter) !== -1;
1149            }
1150
1151            function handleFilterChange(e) {
1152                var needle = (e.currentTarget.value || '').trim();
1153
1154                if (needle.length < 3) {
1155                    $filterForm.addClass('kb-faq__filter--empty');
1156                    currentFilter = '';
1157                    resetFilter();
1158
1159                    $filter.focus();
1160                    return
1161                }
1162
1163                $filterForm.removeClass('kb-faq__filter--empty');
1164                currentFilter = needle.toLowerCase();
1165                updateFilter();
1166            }
1167
1168            function handleFilterClear(e) {
1169                e.preventDefault();
1170
1171                $filter.val('').trigger('input');
1172            }
1173
1174            if ($filterForm.length) {
1175                $container.on('input', '.fn-kb-faq-filter-input', handleFilterChange);
1176                $container.on('click', '.fn-kb-faq-filter-clear', handleFilterClear);
1177            }
1178
1179            var OPEN_SPEED = settings['faq_slow_animation'] ? 400 : 100;
1180
1181            function getMaxHeight(el) {
1182                return Array.prototype.reduce.call(el.childNodes, function(store, current) {
1183                    return store + (current.offsetHeight || 0);
1184                }, 0);
1185            }
1186
1187            /**
1188             * FAQ Toggle
1189             */
1190            function toggleAnswer($item) {
1191                var $answer = $item.find('.fn-kb-faq-answer');
1192                var $link = $item.find('.fn-kb-faq-link');
1193                var answerEl = $answer.get(0);
1194                var maxHeight = getMaxHeight(answerEl);
1195
1196                if ($item.hasClass('kb-faq__questions-list-item--open')) {
1197                    $answer.css('max-height', maxHeight);
1198                    $answer.animate({maxHeight: 0}, OPEN_SPEED, 'swing', function() {
1199                        $item.removeClass('kb-faq__questions-list-item--open');
1200                    });
1201
1202                    if (settings['faq_url_update']) {
1203                        var hashFaqId = getHashFaqId();
1204
1205                        if (hashFaqId && hashFaqId == $link.data('id')) {
1206                            clearHash();
1207                        }
1208                    }
1209                } else {
1210                    if (settings['faq_toggle_mode']) {
1211                        sections.forEach(function(section) {
1212                            section.items.forEach(function(item) {
1213                                closeAnswer(item.$el);
1214                            });
1215                        });
1216                    }
1217
1218                    $answer.animate({maxHeight: maxHeight}, OPEN_SPEED, 'swing', function() {
1219                        $answer.css('max-height', 'none');
1220                        $item.addClass('kb-faq__questions-list-item--open');
1221                    });
1222
1223                    if (settings['faq_url_update']) {
1224                        setHashFaq($link.data('id'));
1225                    }
1226                }
1227            }
1228
1229            function openAnswer($item) {
1230                var $answer = $item.find('.fn-kb-faq-answer');
1231                var answerEl = $answer.get(0);
1232                var maxHeight = getMaxHeight(answerEl);
1233
1234                if (!$item.hasClass('kb-faq__questions-list-item--open')) {
1235                    $answer.animate({maxHeight: maxHeight}, OPEN_SPEED, 'swing', function() {
1236                        $answer.css('max-height', 'none');
1237                        $item.addClass('kb-faq__questions-list-item--open');
1238                    });
1239                }
1240            }
1241
1242            function closeAnswer($item) {
1243                var $answer = $item.find('.fn-kb-faq-answer');
1244                var answerEl = $answer.get(0);
1245                var maxHeight = getMaxHeight(answerEl);
1246
1247                if ($item.hasClass('kb-faq__questions-list-item--open')) {
1248                    $answer.css('max-height', maxHeight);
1249                    $answer.animate({maxHeight: 0}, OPEN_SPEED, 'swing', function() {
1250                        $item.removeClass('kb-faq__questions-list-item--open');
1251                    });
1252                }
1253            }
1254
1255            function handleQuestionClick (e) {
1256                e.preventDefault();
1257
1258                var $link = $(e.currentTarget);
1259                var $item = $link.parent();
1260
1261                toggleAnswer($item);
1262            }
1263
1264            function handleToggleAllClick (e) {
1265                e.preventDefault();
1266
1267                var $link = $(e.currentTarget);
1268                var isOpen = $link.hasClass('kb-faq__toggle-all-link--open');
1269
1270                sections.forEach(function(section) {
1271                    section.items.forEach(function(item) {
1272                        isOpen ? closeAnswer(item.$el) : openAnswer(item.$el);
1273                    });
1274                });
1275
1276                $link.toggleClass('kb-faq__toggle-all-link--open');
1277            }
1278
1279            $container.on('click', '.fn-kb-faq-link', handleQuestionClick);
1280            $container.on('click', '.fn-kb-faq-toggle-all', handleToggleAllClick);
1281        });
1282
1283        if (settings['faq_url_update']) {
1284            // navigate to faq from hash
1285            var hashFaqId = getHashFaqId();
1286            var $faqLink = hashFaqId && $('.fn-kb-faq-link[data-id="' + hashFaqId + '"]');
1287            var scrollOffset = parseInt(settings['faq_scroll_offset']['size']);
1288
1289            if ($faqLink && $faqLink.length) {
1290                $faqLink.trigger('click');
1291
1292                setTimeout(function() {
1293                    $doc.animate({
1294                        scrollTop: $faqLink.offset().top - scrollOffset
1295                    }, 300);
1296                }, 800);
1297            }
1298        }
1299    }
1300
1301    /**
1302     * Content Tree
1303     */
1304    function setupContentTreeWidgets() {
1305        var $contentTree = $('.mkb_content_tree_widget');
1306        var openActiveBranch = settings['content_tree_widget_open_active_branch'];
1307
1308        function setListMaxHeight(index, list) {
1309            var $list = $(list);
1310            $list.animate({'max-height': list.scrollHeight}, 200, function() {
1311                $list.css('max-height', 'none');
1312            });
1313        }
1314
1315        $contentTree.each(function(index, tree) {
1316            var $tree  = $(tree);
1317            var $topics = $tree.find('.mkb-widget-content-tree__topic');
1318
1319            $tree.on('click', '.mkb-widget-content-tree__topic-name', function(e) {
1320                var topicName = e.currentTarget;
1321                var $topic = $(topicName).parent();
1322                var topic = $topic.get(0);
1323
1324                if ($topic.hasClass('topic-open')) {
1325                    $topic.removeClass('topic-open');
1326                    $topic.find('>ul').css('max-height', '0');
1327                } else {
1328                    var activeBranch = [topic.dataset.id];
1329                    var $parents = $topic.parents('.mkb-widget-content-tree__topic');
1330
1331                    $parents.each(function(index, parent) {
1332                        activeBranch.push(parent.dataset.id);
1333                    });
1334
1335                    // hide topics that are not in current branch
1336                    $topics.each(function(index, item) {
1337                        if (activeBranch.indexOf(item.dataset.id) !== -1) {
1338                            return;
1339                        }
1340
1341                        var $item = $(item);
1342
1343                        $item.removeClass('topic-open');
1344                        $item.find('>ul').css('max-height', '0');
1345                    });
1346
1347                    $topic.find('>ul').each(setListMaxHeight);
1348                    $topic.addClass('topic-open');
1349
1350                    $parents.each(function(index, item) {
1351                        var $parentTopic = $(item);
1352
1353                        if ($parentTopic.hasClass('topic-open')) {
1354                            return;
1355                        }
1356
1357                        $parentTopic.find('>ul').each(setListMaxHeight);
1358                        $parentTopic.addClass('topic-open');
1359                    });
1360                }
1361
1362                e.preventDefault();
1363                e.stopImmediatePropagation();
1364            });
1365
1366            if (openActiveBranch) {
1367                setTimeout(function() {
1368                    $tree.find('.mkb-widget-content-tree__article--active')
1369                        .closest('.mkb-widget-content-tree__topic')
1370                        .find('>.mkb-widget-content-tree__topic-name')
1371                        .trigger('click');
1372                }, 300);
1373            }
1374        });
1375    }
1376
1377    /**
1378     * Client submission form
1379     */
1380    function setupSubmissionForm() {
1381        var $submisionContainers = $('.js-mkb-client-submission');
1382
1383        $submisionContainers.each(function(index, container) {
1384            var $container = $(container);
1385            var $form = $container.find('.js-mkb-client-submission-form');
1386
1387            var $title = $container.find('.js-mkb-submission-title');
1388            var $topic = $container.find('.js-mkb-submission-topic');
1389            var $content = $container.find('#mkb-client-editor');
1390            var $antispamAnswer = $container.find('.js-mkb-real-human-answer');
1391            var $messagesContainer = $container.find('.js-mkb-form-messages');
1392
1393            var toolbarOptions = [
1394                [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
1395                ['bold', 'italic', 'underline', 'strike'],
1396                [{ 'list': 'ordered'}, { 'list': 'bullet' }],
1397                ['link'],
1398                ['blockquote', 'code-block'],
1399                ['clean']
1400            ];
1401
1402            var quillEditor = new Quill('#mkb-client-editor', {
1403                modules: {
1404                    toolbar: toolbarOptions,
1405                    history: {
1406                        delay: 2000,
1407                        maxStack: 500,
1408                        userOnly: true
1409                    }
1410                },
1411                theme: settings.submit_content_editor_skin
1412            });
1413
1414            function getData() {
1415                return {
1416                    action: 'mkb_save_client_submission',
1417                    title: $title.val().trim(),
1418                    content: $content.find('.ql-editor').get(0).innerHTML.trim(),
1419                    topic: $topic.length ? $topic.val().trim() : '',
1420                    antispam: $antispamAnswer.length ? $antispamAnswer.val().trim() : ''
1421                }
1422            }
1423
1424            function validateData(data) {
1425                var status = 0;
1426                var errors = [];
1427
1428                if (!data.title) {
1429                    status = 1;
1430                    errors.push(i18n.submission_empty_title);
1431                }
1432
1433                if (!data.content
1434                        .replace('<p>', '')
1435                        .replace('</p>', '')
1436                        .replace('<br>', '')) {
1437
1438                    status = 1;
1439                    errors.push(i18n.submission_empty_content);
1440                }
1441
1442                if (!data.antispam && $antispamAnswer.length) {
1443                    status = 1;
1444                    errors.push(settings.antispam_failed_message);
1445                }
1446
1447                return {
1448                    status: status,
1449                    errors: errors
1450                }
1451            }
1452
1453            function renderMessages(messages) {
1454                $messagesContainer.html(messages.reduce(function(html, err) {
1455                    return html + '<div class="mkb-form-message">' + err + '</div>'
1456                }), '');
1457            }
1458
1459            function showMessages(messageClass, noScroll) {
1460                noScroll = noScroll || false;
1461
1462                $messagesContainer.addClass(messageClass).removeClass('mkb-hidden');
1463
1464                if (!noScroll) {
1465                    $doc.animate({
1466                        scrollTop: $messagesContainer.offset().top - adminOffset - 30
1467                    }, 100);
1468                }
1469            }
1470
1471            function showErrorMessages(noScroll) {
1472                showMessages('mkb-form-error', noScroll);
1473            }
1474
1475            function showSuccessMessages(noScroll) {
1476                showMessages('mkb-form-success', noScroll);
1477            }
1478
1479            $container.on('click', '.js-mkb-client-submission-send', function(e) {
1480                var requestData = getData();
1481                var $btn = $(e.currentTarget);
1482
1483                if ($btn.hasClass('mkb-disabled')) {
1484                    return;
1485                }
1486
1487                var validationResult = validateData(requestData);
1488
1489                if (validationResult.status !== 0) {
1490                    renderMessages(validationResult.errors);
1491                    showErrorMessages();
1492
1493                    $doc.animate({
1494                        scrollTop: $messagesContainer.offset().top - adminOffset - 30
1495                    }, 100);
1496
1497                    return;
1498                }
1499
1500                $btn.addClass('mkb-disabled');
1501
1502                jQuery.ajax({
1503                    method: 'POST',
1504                    url: ajaxUrl,
1505                    dataType: 'json',
1506                    data: addAjaxNonce(requestData)
1507                }).done(function(response) {
1508
1509                    $btn.removeClass('mkb-disabled');
1510
1511                    if (response.status == 1) {
1512                        renderMessages([response.error]);
1513                        showErrorMessages();
1514                    } else {
1515                        renderMessages([settings.submit_success_message]);
1516                        showSuccessMessages(true);
1517
1518                        $form.html('');
1519
1520                        $doc.animate({
1521                            scrollTop: $container.offset().top - adminOffset - 30
1522                        }, 100);
1523                    }
1524                });
1525            });
1526        });
1527    }
1528
1529    /**
1530     * KB floating helper
1531     */
1532    function setupHelper() {
1533        var $helper = $('.js-mkb-floating-helper');
1534
1535        if (!$helper.length) {
1536            return;
1537        }
1538
1539        var $searchInput = $helper.find('.kb-search__input');
1540
1541        $helper.on('click', '.js-mkb-floating-helper-btn', function() {
1542            $helper.addClass('mkb-floating-helper-wrap--open');
1543            $searchInput.get(0).focus();
1544        });
1545
1546        $helper.on('click', '.js-mkb-floating-helper-close', function() {
1547            $helper.removeClass('mkb-floating-helper-wrap--open');
1548        });
1549
1550        setTimeout(function() {
1551            $helper.addClass('mkb-floating-helper-wrap--ready');
1552        }, settings.fh_show_delay);
1553    }
1554
1555    /**
1556     * Attachments
1557     */
1558    function setupAttachments() {
1559        // download tracking
1560        $body.on('click', '.js-mkb-attachment-link', function(e) {
1561            var link = e.currentTarget;
1562            var id = link.dataset.id;
1563
1564            if (!id) {
1565                return;
1566            }
1567
1568            jQuery.ajax({
1569                method: 'POST',
1570                url: ajaxUrl,
1571                dataType: 'json',
1572                data: addAjaxNonce({
1573                    action: 'mkb_track_attachment_download',
1574                    id: id
1575                })
1576            })
1577        });
1578    }
1579
1580    /**
1581     * Main plugin startup
1582     */
1583    function init() {
1584        $adminBar = $('#wpadminbar');
1585        adminOffset = $adminBar.length ? $adminBar.height() : 0;
1586
1587        if ($kbSearch.length && !isSearchDisabled()) {
1588            initSearchInputs();
1589            $body.on('keydown', onSearchKeyPress);
1590            $body.on('click', '.kb-search__clear', handleSearchClear);
1591            focusInput();
1592            onSearchType($kbSearch.eq(0)); // restore previous search
1593        }
1594
1595        // FAQ items
1596        setupFaq();
1597
1598        // article related code
1599        if (info.isSingle) {
1600            $body.on('click', '.mkb-article-extra__like', handleArticleLike);
1601            $body.on('click', '.mkb-article-extra__dislike', handleArticleDislike);
1602            $body.on('click', '.mkb-article-extra__feedback-form-submit', handleFeedbackSubmit);
1603            $body.on('input', '.mkb-article-extra__feedback-form-message-area', handleFeedbackType);
1604
1605            handleArticleBackToTop();
1606            handleArticleTOC();
1607
1608            if (settings['article_fancybox']) {
1609                initArticlesFancyBox();
1610            }
1611
1612            trackArticleView();
1613
1614            // sticky articles sidebar
1615            setupArticleStickySidebar();
1616
1617            setupAttachments();
1618        }
1619
1620        setupContentTreeWidgets();
1621        setupSubmissionForm();
1622        setupHelper();
1623    }
1624
1625    // start
1626    $(document).ready(init);
1627
1628})(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.