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.