1/** 2 * Project: Minerva KB 3 * Copyright: 2015-2016 @KonstruktStudio 4 */ 5(function ($) { 6 7 var GLOBAL_DATA = window.MinervaKB; 8 var ui = window.MinervaCommonUI; 9 10 var i18n = GLOBAL_DATA.i18n; 11 var platform = GLOBAL_DATA.platform; 12 var settings = GLOBAL_DATA.settings; 13 var info = GLOBAL_DATA.info; 14 15 var $body = $('body'); 16 17 /** 18 * libs 19 */ 20 if (!String.prototype.trim) { 21 String.prototype.trim = function () { 22 return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 23 }; 24 } 25 26 function simpleExtend(obj1, obj2) { 27 for (var attr in obj2) { 28 if (obj2.hasOwnProperty(attr)) obj1[attr] = obj2[attr]; 29 } 30 31 return obj1; 32 } 33 34 /** 35 * Gets form data for ajax calls 36 * @param form 37 * @returns {} 38 */ 39 function getFormData(form) { 40 return $(form).serializeArray().reduce(function(store, kv) { 41 store[kv.name] = kv.value; 42 return store; 43 }, {}); 44 } 45 46 /** 47 * Debounces function execution 48 * TODO: make shared utils lib 49 * @param func 50 * @param wait 51 * @param immediate 52 * @returns {Function} 53 */ 54 function debounce(func, wait, immediate) { 55 var timeout; 56 return function () { 57 var context = this, args = arguments; 58 var later = function () { 59 timeout = null; 60 if (!immediate) { 61 func.apply(context, args); 62 } 63 }; 64 var callNow = immediate && !timeout; 65 clearTimeout(timeout); 66 timeout = setTimeout(later, wait); 67 if (callNow) { 68 func.apply(context, args); 69 } 70 }; 71 } 72 73 /** 74 * Throttles function execution. Based on Ben Alman implementation 75 * TODO: make shared utils lib 76 * @param delay 77 * @param noTrailing 78 * @param callback 79 * @param atBegin 80 * @returns {wrapper} 81 */ 82 function throttle(delay, noTrailing, callback, atBegin) { 83 var timeoutId; 84 var lastExec = 0; 85 86 if (typeof noTrailing !== 'boolean') { 87 atBegin = callback; 88 callback = noTrailing; 89 noTrailing = undefined; 90 } 91 92 function wrapper() { 93 var elapsed = +new Date() - lastExec; 94 var args = arguments; 95 96 var exec = function _exec() { 97 lastExec = +new Date(); 98 callback.apply(this, args ); 99 }.bind(this); 100 101 function clear() { 102 timeoutId = undefined; 103 } 104 105 if (atBegin && !timeoutId) { 106 exec(); 107 } 108 109 timeoutId && clearTimeout(timeoutId); 110 111 if (atBegin === undefined && elapsed > delay) { 112 exec(); 113 } else if (noTrailing !== true) { 114 timeoutId = setTimeout( 115 atBegin ? 116 clear : 117 exec, 118 atBegin === undefined ? 119 delay - elapsed : 120 delay 121 ); 122 } 123 } 124 125 return wrapper; 126 } 127 128 function addAjaxNonce(data) { 129 data['nonce_key'] = GLOBAL_DATA.nonce.nonceKey; 130 data['nonce_value'] = GLOBAL_DATA.nonce.nonce; 131 132 return data; 133 } 134 135 function addAjaxNonceToFormData(formData) { 136 formData.append('nonce_key',GLOBAL_DATA.nonce.nonceKey); 137 formData.append('nonce_value',GLOBAL_DATA.nonce.nonce); 138 139 return formData; 140 } 141 142 /** 143 * Sends Google Analytics event, if API available 144 * @param category 145 * @param action 146 * @param label 147 * @param value 148 */ 149 function trackGoogleAnalytics(category, action, label, value) { 150 if (window.ga && typeof window.ga === 'function') { 151 window.ga('send', 'event', category, action, label, value, { 152 nonInteraction: true 153 }); 154 } 155 } 156 157 // theme 158 var ajaxUrl = GLOBAL_DATA.ajaxUrl; 159 var $kbSearch = $('.kb-search__input'); 160 var NO_RESULTS_CLASS = 'kb-search__input-wrap--no-results'; 161 var HAS_CONTENT_CLASS = 'kb-search__input-wrap--has-content'; 162 var HAS_RESULTS_CLASS = 'kb-search__input-wrap--has-results'; 163 var REQUEST_CLASS = 'kb-search__input-wrap--request'; 164 var hasResults = false;
165 var resultsCount = 0; 166 var activeResult = -1; 167 var ESC = 27; 168 var ENTER = 13; 169 var ARROW_LEFT = 37; 170 var ARROW_UP = 38; 171 var ARROW_RIGHT = 39; 172 var ARROW_DOWN = 40; 173 var $doc = $('html, body'); 174 var $adminBar = $('#wpadminbar'); 175 var adminOffset = $adminBar.length ? $adminBar.height() : 0; 176 var searchMode = settings['search_mode']; 177 var searchNeedleLength = parseInt(settings['search_needle_length']); 178 var searchRequestsCount = 0; 179 var searchCache = {}; 180 var trackingCache = {}; 181 var onPageFAQItems = {}; 182 var onPageGlossaryItems = {}; 183 184 function setupExtraSearchGroupsOnPageTargets() { 185 // 1. FAQ 186 if (settings['active_search_groups'].includes('faq')) { 187 var hasOnPageFAQItems = false; 188 189 var $faqContainer = $('.fn-kb-faq-container'); 190 191 if (!$faqContainer.length) { 192 return; 193 } 194 195 $faqContainer.each(function(wrapIndex, item) { 196 var $wrap = $(item); 197 198 $wrap.find('.fn-kb-faq-link').each(function(index, item) { 199 var id = item.dataset.id; 200 201 if (!onPageFAQItems[id]) { 202 onPageFAQItems[id] = { 203 el: item, 204 wrapIndex: wrapIndex 205 }; 206 hasOnPageFAQItems = true; 207 } 208 }); 209 }); 210 211 if (hasOnPageFAQItems) { 212 $body.on('click', '.js-mkb-search-results-group-faq a', function(e) { 213 var id = e.currentTarget.dataset.id; 214 215 if (onPageFAQItems[id]) { 216 e.preventDefault(); 217 e.stopImmediatePropagation(); 218 219 scrollToFAQItem(id); 220 } 221 }); 222 } 223 } 224 225 // 2. Glossary 226 if (settings['active_search_groups'].includes('glossary')) { 227 var hasOnPageGlossaryItems = false; 228 var $glossaryContainer = $('.js-mkb-glossary-list'); 229 230 if (!$glossaryContainer.length) { 231 return; 232 } 233 234 $glossaryContainer.each(function(wrapIndex, item) { 235 var $wrap = $(item); 236 237 $wrap.find('.js-mkb-glossary-term-entry').each(function(index, item) { 238 var id = item.dataset.id; 239 240 if (!onPageGlossaryItems[id]) { 241 onPageGlossaryItems[id] = { 242 el: item, 243 wrapIndex: wrapIndex 244 }; 245 hasOnPageGlossaryItems = true; 246 } 247 }); 248 }); 249 250 if (hasOnPageGlossaryItems) { 251 $body.on('click', '.js-mkb-search-results-group-glossary a', function(e) { 252 var id = e.currentTarget.dataset.id; 253 254 if (onPageGlossaryItems[id]) { 255 e.preventDefault(); 256 e.stopImmediatePropagation(); 257 258 scrollToGlossaryItem(id); 259 } 260 }); 261 } 262 } 263 } 264 265 function scrollToFAQItem(id) { 266 var faqItem = onPageFAQItems[id]; 267 268 if (faqItem) { 269 var $question = $(faqItem.el); 270 var scrollOffset = parseInt(settings['faq_scroll_offset']['size']); 271 272 $body.trigger({ 273 type: 'minerva_faq_toggle_answer_' + faqItem.wrapIndex, 274 id: id 275 }); 276 277 setTimeout(function() { 278 $doc.animate({ 279 scrollTop: $question.offset().top - scrollOffset 280 }, 300); 281 }, 110); // programmatic faq open is 100ms 282 } 283 } 284 285 function scrollToGlossaryItem(id) { 286 var glossaryItem = onPageGlossaryItems[id]; 287 288 if (glossaryItem) { 289 var scrollOffset = parseInt(settings['glossary_scroll_offset']['size']); 290 291 $doc.animate({ 292 scrollTop: $(glossaryItem.el).offset().top - scrollOffset 293 }, 300); 294 } 295 } 296 297 /** 298 * Live search result handler 299 * @param $search 300 * @param response 301 */ 302 function handleSearchResultsReceive($search, response) { 303 var $wrap = $search.parents('.kb-search__input-wrap'); 304 var $summary = $wrap.find('.kb-summary-text-holder'); 305 var $results = $wrap.find('.kb-search__results'); 306 var results = response.result; 307 var extraResults = response.extraResults || {}; 308 var faqResults = (extraResults.faq || []).filter(function(result) { 309 return settings['faq_enable_pages'] || onPageFAQItems[result.id]; 310 }); 311 var glossaryResults = (extraResults.glossary || []).filter(function(result) { 312 return settings['glossary_enable_pages'] || onPageGlossaryItems[result.id]; 313 }); 314 var topicsResults = extraResults.topics || []; 315 var extraResultsCount = faqResults.length + glossaryResults.length + topicsResults.length; 316 var totalResults = resultsCount = results.length + extraResultsCount; 317 var searchNeedle = response.search; 318 var resultsContent; 319 var resultsInfoHtml = response.results_info || ''; 320 var searchShowTopics = $search.data('show-results-topic') === 1; 321 var showTopicsLabel = $search.data('topic-label'); 322 var useCustomTopicColors = Boolean($search.data('custom-topic-colors')); 323 var showExcerpt = settings['live_search_show_excerpt']; 324 325 if (searchMode === 'nonblocking') {
326 var needle = $search.val() && $search.val().trim(); 327 328 if (!needle || needle.length < searchNeedleLength) { 329 // in nonblocking mode user could have already removed the typed string 330 331 results = []; 332 hasResults = false; 333 resultsCount = 0; 334 activeResult = -1; 335 $wrap.removeClass(HAS_RESULTS_CLASS).removeClass(NO_RESULTS_CLASS); 336 $summary.html(''); 337 $results.html(''); 338 339 return; 340 } 341 } 342 343 $results.html(''); 344 345 /** 346 * 0. No results 347 */ 348 if (!results && !results.length && !faqResults.length && !glossaryResults.length) { 349 if (settings['track_search_without_results']) { 350 trackGoogleAnalytics( 351 settings['ga_bad_search_category'], 352 settings['ga_bad_search_action'], 353 searchNeedle, 354 settings['ga_bad_search_value'] || 0 355 ); 356 } 357 358 hasResults = false; 359 resultsCount = 0; 360 activeResult = -1; 361 $wrap.removeClass(HAS_RESULTS_CLASS).addClass(NO_RESULTS_CLASS); 362 $summary.html(i18n['no-results']); 363 } 364 365 function getResultsGroupHTML(label, count, groupId) { 366 return $( 367 '<ul class="js-mkb-search-results-group-' + groupId + '">' + 368 '<li>' + 369 '<span class="kb-search__results-group">' + 370 '<span class="kb-search__results-group-title">' + label + '</span>' + 371 '<span class="kb-search__results-group-count">' + 372 + count + ' ' + 373 (count === 1 ? i18n['result'] : i18n['results']) + 374 '</span>' + 375 '</span>' + 376 '</li>' + 377 '</ul>' 378 ) 379 } 380 381 hasResults = true; 382 activeResult = -1; 383 384 $wrap.removeClass(NO_RESULTS_CLASS).addClass(HAS_RESULTS_CLASS); 385 $summary.html(totalResults + ' ' + (totalResults === 1 ? i18n['result'] : i18n['results'])); 386 387 /** 388 * 1. KB results 389 */ 390 if (results.length) { 391 if (settings['track_search_with_results']) { 392 trackGoogleAnalytics( 393 settings['ga_good_search_category'], 394 settings['ga_good_search_action'], 395 searchNeedle, 396 settings['ga_good_search_value'] || 0 397 ); 398 } 399 400 // TODO: regroup product label for grouped / standalone results 401 // TODO: maybe switch to FE label 402 var $kbResultsInitialHTML = extraResultsCount ? 403 getResultsGroupHTML(i18n['search_group_kb'], results.length, 'kb') : 404 $('<ul></ul>'); 405 406 $kbResultsInitialHTML.append(resultsInfoHtml); 407 408 resultsContent = results.reduce(function ($el, result) { 409 var isTopicPresent = Boolean(result.topics[0]); 410 var isProductArticle = Boolean(result.product); 411 var firstTopic = result.topics[0]; 412 var topicColorStyle = isTopicPresent && useCustomTopicColors ? 413 'style="background-color: ' + firstTopic.color + '"' : 414 ''; 415 416 return $el.append( 417 '<li>' + 418 '<a href="' + result.link + '">' + 419 '<span class="kb-search__result-header">' + 420 '<span class="kb-search__result-title">' + result.title + '</span>' + 421 (searchShowTopics && isTopicPresent ? 422 '<span class="kb-search__result-topic">' + 423 '<span class="kb-search__result-topic-label">' + showTopicsLabel + '</span>' + 424 '<span class="kb-search__result-topic-name" ' + topicColorStyle + '>' + 425 (isProductArticle && result.product !== firstTopic.name ? 426 result.product + ' / ' : '') + (firstTopic.name) + 427 '</span>' + 428 '</span>' : 429 '') + 430 '</span>' + 431 (showExcerpt ? '<span class="kb-search__result-excerpt">' + result.excerpt + ' ...</span>' : '') + 432 '</a>' + 433 '</li>' 434 ); 435 }, $kbResultsInitialHTML); 436 } 437 438 /** 439 * 2. FAQ results 440 */ 441 if (faqResults.length) { 442 var faqContent = faqResults.reduce(function($el, result) { 443 return $el.append('<li><a href="' + result.link + '" data-id="' + result.id + '">' + result.title + '</a></li>'); 444 }, getResultsGroupHTML(i18n['search_group_faq'], faqResults.length, 'faq')); 445 } 446 447 /** 448 * 3. Glossary results 449 */ 450 if (glossaryResults.length) { 451 var glossaryContent = glossaryResults.reduce(function($el, result) { 452 return $el.append('<li><a href="' + result.link + '" data-id="' + result.id + '">' + result.title + '</a></li>'); 453 }, getResultsGroupHTML(i18n['search_group_glossary'], glossaryResults.length, 'glossary')); 454 } 455 456 /** 457 * 4. Topics results 458 */ 459 if (topicsResults.length) { 460 var topicsContent = topicsResults.reduce(function($el, result) { 461 return $el.append('<li><a href="' + result.link + '">' + result.title + '</a></li>'); 462 }, getResultsGroupHTML(i18n['search_group_kb_topics'], topicsResults.length, 'topics')); 463 } 464 465 var resultGroups = settings['active_search_groups']; 466 467 resultGroups.forEach(function(groupId) { 468 switch(groupId) { 469 case 'kb': 470 $results.append(resultsContent); 471 break; 472 473 case 'faq': 474 $results.append(faqContent); 475 break; 476 477 case 'glossary': 478 $results.append(glossaryContent); 479 break; 480 481 case 'topics': 482 $results.append(topicsContent); 483 break; 484 485 default: 486 break; 487 } 488 }); 489 } 490 491 function focusInput() { 492 $kbSearch.filter('[data-autofocus="1"]').focus(); 493 } 494 495 function nextSearchResult() { 496 var $resultItems = $('.kb-search__results li a'); 497 498 activeResult = activeResult + 1 >= resultsCount ? 0 : activeResult + 1; 499 $resultItems.eq(activeResult).focus(); 500 } 501 502 function prevSearchResult() { 503 var $resultItems = $('.kb-search__results li a'); 504 505 activeResult = activeResult - 1 < 0 ? resultsCount - 1 : activeResult - 1; 506 $resultItems.eq(activeResult).focus(); 507 } 508 509 /** 510 * Live search keypress handler 511 * @param e 512 */ 513 function onSearchKeyPress(e) { 514 515 if (!$(".kb-search__input").is(":focus") && !$(".kb-search__results a").is(":focus")) { 516 return; //we do not to mess with keypress unless search is in focus 517 } 518 519 var $search = $(".kb-search__input:focus"); 520 521 switch (e.keyCode) { 522 case ESC: 523 focusInput(); 524 break; 525 526 case ARROW_UP: 527 prevSearchResult(); 528 break; 529 530 case ARROW_DOWN: 531 nextSearchResult(); 532 break; 533 534 case ENTER: 535 if ($search.length && !$search.val().trim()) { 536 e.preventDefault(); 537 e.stopImmediatePropagation(); 538 } 539 return; 540 541 default: 542 return; 543 } 544 545 e.preventDefault(); // prevent the default action (scroll / move caret) 546 } 547
548 function serializeRequestParams(params) { 549 var serialized = ""; 550 551 for (var key in params) { 552 if (serialized !== "") { 553 serialized += "&"; 554 } 555 556 serialized += key + "=" + encodeURIComponent(params[key]); 557 } 558 559 return serialized; 560 } 561 562 563 564 /** 565 * Search request stats is saved separately for non-blocking search 566 */ 567 function trackSearchRequest(searchParams) { 568 569 searchParams.trackResults = true; 570 571 return $.ajax({ 572 method: 'POST', 573 url: ajaxUrl, 574 dataType: 'json', 575 data: searchParams 576 }); 577 } 578 579 /** 580 * Waits for a timeout untill tracking results 581 * @param searchParams 582 */ 583 var trackingTimerId = null; 584 585 function handleNonBlockingTracking(searchParams) { 586 var serializedParams = serializeRequestParams(searchParams); 587 588 if (trackingTimerId) { 589 clearTimeout(trackingTimerId); 590 } 591 592 if (trackingCache[serializedParams]) { 593 return; 594 } 595 596 trackingTimerId = setTimeout(function() { 597 trackSearchRequest(searchParams).then(function() { 598 trackingCache[serializedParams] = true; 599 }); 600 }, 1000); 601 } 602 603 /** 604 * Lice search type handler 605 * @param $search 606 */ 607 function onSearchType($search) { 608 var $wrap = $search.parents('.kb-search__input-wrap'); 609 var needle = $search.val() && $search.val().trim(); 610 var $topics = $wrap.find('input[name="topics"]'); 611 var topics = $topics.length ? $topics.val() : null; 612 var $kbId = $wrap.find('input[name="kb_id"]'); 613 var $lang = $wrap.find('input[name="lang"]'); 614 var kbId = $kbId.length ? parseInt($kbId.val()) : null; 615 var searchParams = { 616 action: 'mkb_kb_search', 617 search: needle, 618 mode: searchMode 619 }; 620 621 if (kbId) { 622 searchParams.kb_id = kbId; 623 } 624 625 if (topics) { 626 searchParams.topics = topics; 627 } 628 629 if ($lang.length && $lang.val()) { 630 searchParams.lang = $lang.val(); 631 } 632
633 var serializedParams = serializeRequestParams(searchParams); 634 635 if (needle) { 636 $wrap.addClass(HAS_CONTENT_CLASS); 637 } else { 638 $wrap.removeClass(HAS_CONTENT_CLASS); 639 } 640 641 // check cache for response 642 if (settings['search_request_fe_cache'] && searchCache[serializedParams]) { 643 // track cached result, if it wasn tracked before 644 if (searchMode === 'nonblocking') { 645 handleNonBlockingTracking(searchParams); 646 } 647 648 return handleSearchResultsReceive.call(this, $search, searchCache[serializedParams]); 649 } 650 651 if (!needle || needle.length < searchNeedleLength) { 652 hasResults = false; 653 resultsCount = 0; 654 activeResult = -1; 655 $wrap.removeClass(HAS_RESULTS_CLASS).removeClass(NO_RESULTS_CLASS); 656 657 if (searchMode === 'nonblocking' && needle.length > 0) { 658 fakeRequest($wrap); // progress indicator for input to be more responsive 659 } 660 661 return; 662 } 663 664 if (searchMode === 'nonblocking') { 665 handleNonBlockingTracking(searchParams); 666 } 667 668 if (searchMode === 'blocking') { 669 $search.attr('disabled', 'disabled'); 670 } 671 672 $wrap.addClass(REQUEST_CLASS); 673 ++searchRequestsCount; 674 675 $.ajax({ 676 method: settings['live_search_use_post'] ? 'POST' : 'GET', 677 url: ajaxUrl, 678 dataType: 'json', 679 data: searchParams 680 }) 681 .then(function(response) { 682 if (settings['search_request_fe_cache']) { 683 searchCache[serializedParams] = response; 684 } 685 686 return handleSearchResultsReceive.call(this, $search, response); 687 }.bind(this)) 688 .always(function () { 689 if (searchMode === 'blocking') { 690 $search 691 .attr('disabled', false) 692 .focus(); 693 } 694 695 --searchRequestsCount; 696 697 if (searchRequestsCount === 0) { 698 $wrap.removeClass(REQUEST_CLASS); 699 } 700 }); 701 } 702 703 /** 704 * Progress indicator for short requests 705 * @param $wrap 706 */
707 function fakeRequest($wrap) { 708 $wrap.addClass(REQUEST_CLASS); 709 ++searchRequestsCount; 710 711 setTimeout(function() { 712 --searchRequestsCount; 713 if (searchRequestsCount === 0) { 714 $wrap.removeClass(REQUEST_CLASS); 715 } 716 }, 500); 717 } 718 719 /** 720 * Article pageview tracking 721 */ 722 function trackArticleView() { 723 var $tracking_meta = $('.mkb-article-extra__tracking-data'); 724 725 if (!$tracking_meta.length) { 726 return; 727 } 728 729 var $id = $tracking_meta.data('article-id'); 730 731 if (!$id) { 732 return; 733 } 734 735 $.ajax({ 736 method: 'POST', 737 url: ajaxUrl, 738 dataType: 'json', 739 data: addAjaxNonce({ 740 action: 'mkb_article_pageview', 741 id: $id 742 }) 743 }); 744 } 745 746 /** 747 * Article like 748 * @param e 749 */ 750 function handleArticleLike(e) { 751 e.preventDefault(); 752 753 var $likeBtn = $(e.currentTarget); 754 var id = $likeBtn.data('article-id'); 755 var title = $likeBtn.data('article-title'); 756 var $count = $('.mkb-article-extra__stats-likes'); 757 var likes = parseInt($count.text(), 10); 758 759 if (!id || $likeBtn.hasClass('mkb-voted') || $likeBtn.hasClass('mkb-disabled')) { 760 return; 761 } 762 763 $likeBtn.addClass('mkb-voted'); 764 $('.mkb-article-extra__dislike').addClass('mkb-disabled'); 765 $count.text(++likes); 766 767 $.ajax({ 768 method: 'POST', 769 url: ajaxUrl, 770 dataType: 'json', 771 data: addAjaxNonce({ 772 action: 'mkb_article_like', 773 id: id 774 }) 775 }).done(function() { 776 if (settings['track_article_likes']) { 777 trackGoogleAnalytics( 778 settings['ga_like_category'], 779 settings['ga_like_action'], 780 settings['ga_like_label'] === 'article_title' ? title : id, 781 settings['ga_like_value'] || 0 782 ); 783 } 784 785 if (settings['show_like_message']) { 786 $('.fn-rating-likes-block') 787 .html($('<div class="mkb-article-extra__message">' + i18n['like_message_text'] + '</div>')); 788 } 789 790 if (settings['enable_feedback'] && 791 (settings['feedback_mode'] === 'like' || settings['feedback_mode'] === 'any')) { 792 addFeedbackForm(); 793 } 794 }); 795 } 796 797 /** 798 * Article dislike 799 * @param e 800 */ 801 function handleArticleDislike(e) { 802 e.preventDefault(); 803 804 var $dislikeBtn = $(e.currentTarget); 805 var id = $dislikeBtn.data('article-id'); 806 var title = $dislikeBtn.data('article-title'); 807 var $count = $('.mkb-article-extra__stats-dislikes'); 808 var dislikes = parseInt($count.text(), 10); 809 810 if (!id || $dislikeBtn.hasClass('mkb-voted') || $dislikeBtn.hasClass('mkb-disabled')) { 811 return; 812 } 813 814 $dislikeBtn.addClass('mkb-voted'); 815 $('.mkb-article-extra__like').addClass('mkb-disabled'); 816 $count.text(++dislikes); 817 818 $.ajax({ 819 method: 'POST', 820 url: ajaxUrl, 821 dataType: 'json', 822 data: addAjaxNonce({ 823 action: 'mkb_article_dislike', 824 id: id 825 }) 826 }).done(function() { 827 if (settings['track_article_dislikes']) { 828 trackGoogleAnalytics( 829 settings['ga_dislike_category'], 830 settings['ga_dislike_action'], 831 settings['ga_dislike_label'] === 'article_title' ? title : id, 832 settings['ga_dislike_value'] || 0 833 ); 834 } 835 836 if (settings['show_dislike_message']) { 837 $('.fn-rating-likes-block') 838 .html($('<div class="mkb-article-extra__message">' + i18n['dislike_message_text'] + '</div>')); 839 } 840 841 if (settings['enable_feedback'] && 842 (settings['feedback_mode'] === 'dislike' || settings['feedback_mode'] === 'any')) { 843 addFeedbackForm(); 844 } 845 }); 846 } 847 848 /**
849 * Renders feedback form if configured 850 */ 851 function addFeedbackForm() { 852 $('.fn-article-feedback-container').append($( 853 '<div class="mkb-article-extra__feedback-form mkb-article-extra__feedback-form--no-content fn-feedback-form">' + 854 855 (settings['feedback_email_on'] ? 856 ( 857 '<div class="mkb-article-extra__feedback-form-email-title">' + i18n['feedback_email_label'] + '</div>' + 858 '<input type="email" name="mkb_feedback_email" class="mkb-article-extra__feedback-form-email js-mkb-feedback-email">' 859 ) : 860 '') + 861 862 '<div class="mkb-article-extra__feedback-form-title">' + 863 i18n['feedback_label'] + 864 '</div>' + 865 '<div class="mkb-article-extra__feedback-form-message">' + 866 '<textarea class="mkb-article-extra__feedback-form-message-area js-mkb-feedback-message" rows="5"></textarea>' + 867 (i18n['feedback_info_text'] ? 868 ('<div class="mkb-article-extra__feedback-info">' + i18n['feedback_info_text'] + '</div>') : 869 '') + 870 '</div>' + 871 '<div class="mkb-article-extra__feedback-form-submit">' + 872 '<a href="#">' + i18n['feedback_submit_label'] + '</a>' + 873 '</div>' + 874 '</div>' 875 )); 876 } 877 878 /** 879 * Sends article feedback to server 880 * @param e 881 */ 882 function handleFeedbackSubmit(e) { 883 var $trackingMeta = $('.mkb-article-extra__tracking-data'); 884 885 e.preventDefault(); 886 887 if (!$trackingMeta.length) { 888 return; 889 } 890 891 var id = $trackingMeta.data('article-id'); 892 var title = $trackingMeta.data('article-title'); 893 var $btn = $(e.target); 894 var $content = $('.js-mkb-feedback-message'); 895 var $email = $('.js-mkb-feedback-email'); 896 var email = Boolean($email.length) && $email.val().trim(); 897 898 if (!id || !$content.val()) { 899 return; 900 } 901 902 $btn
903 .text(i18n['feedback_submit_request_label']) 904 .attr('disabled', 'disabled'); 905 906 var formData = { 907 action: 'mkb_article_feedback', 908 id: id, 909 content: $content.val() 910 }; 911 912 if (email) { 913 formData.email = email; 914 } 915 916 $.ajax({ 917 method: 'POST', 918 url: ajaxUrl, 919 dataType: 'json', 920 data: addAjaxNonce(formData) 921 }).done(function() { 922 if (settings['track_article_feedback']) { 923 trackGoogleAnalytics( 924 settings['ga_feedback_category'], 925 settings['ga_feedback_action'], 926 settings['ga_feedback_label'] === 'article_title' ? title : id, 927 settings['ga_feedback_value'] || 0 928 ); 929 } 930 931 $('.fn-article-feedback-container').html( 932 '<div class="mkb-article-extra__feedback-sent-message">' + 933 i18n['feedback_sent_text'] + 934 '</div>' 935 ); 936 }); 937 } 938 939 /** 940 * Toggle submit available 941 * @param e 942 */ 943 function handleFeedbackType(e) { 944 $('.fn-feedback-form').toggleClass('mkb-article-extra__feedback-form--no-content', Boolean($(e.currentTarget).val() < 1)); 945 } 946 947 /** 948 * Back to top in articles 949 */ 950 function handleArticleBackToTop() { 951 var $container = $('.mkb-container'); 952 953 $container.on('click', '.mkb-back-to-top', function (e) { 954 e.preventDefault(); 955 956 $doc.animate({ 957 scrollTop: settings['back_to_site_top'] ? 0 : $container.offset().top - adminOffset 958 }, 300); 959 960 window.location.hash = ''; 961 }); 962 } 963 964 /** 965 * Article Table of Contents 966 */ 967 function handleArticleTOC() { 968 var $entryContent = $('.mkb-article-text'); 969 var $tocList = $('.mkb-anchors-list'); 970 var scrollOffset = parseInt(settings['toc_scroll_offset']['size']); 971 var headingsExclude = settings['toc_headings_exclude'].trim().toLowerCase(); 972 var $headings; 973 var isScrollSpy = settings['scrollspy_switch'] && settings['toc_in_content_disable'] && 974 platform === 'desktop' && 975 window.outerWidth >= parseInt(settings['article_sidebar_sticky_min_width']['size'], 10); 976 977 // dynamic TOC 978 if ($tocList.hasClass('mkb-anchors-list--dynamic')) { 979 var headingsPool = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; 980 981 if (headingsExclude) { 982 var excluded = headingsExclude.split(',').map(function(heading) { 983 return heading.trim(); 984 }).filter(function(heading) { 985 return Boolean(heading); 986 }); 987 988 headingsPool = headingsPool.filter(function(heading) { 989 return excluded.indexOf(heading) === -1; 990 }); 991 } 992 993 $headings = $entryContent.find(headingsPool.join(', ')); 994 995 if (settings['show_back_to_top']) { 996 wrapTOCAnchors($headings); 997 } 998 999 } else { // via shortcodes 1000 $headings = $('.mkb-anchor__title'); 1001 } 1002 1003 // scroll 1004 var headingOffsets = Array.prototype.map.call($headings, function(heading) { 1005 return $(heading).offset().top; 1006 }); 1007 var $links = $tocList.find('.mkb-anchors-list__item-link'); 1008 var linksArr = Array.prototype.map.call($links, function(link) { 1009 return link; 1010 }).filter(function(link) { 1011 return link.dataset.index !== '-1'; // other pages links 1012 }); 1013 1014 function recalculateMetrics() { 1015 headingOffsets = Array.prototype.map.call($headings, function(heading) { 1016 return $(heading).offset().top; 1017 }); 1018 } 1019 1020 $(window).on('load', recalculateMetrics); 1021 $(window).on('resize', debounce(recalculateMetrics, 1000)); 1022 1023 setTimeout(recalculateMetrics, 3000); // in case load fails 1024 1025 var navScrolling = false; 1026 1027 function navigateToChapter(index) { 1028 navScrolling = true; 1029 1030 recalculateMetrics(); 1031 1032 if (settings['toc_url_update']) { // TODO: replace with browserHistory instead 1033 window.location.hash = 'ch_' + (index + 1); 1034 } 1035 1036 $doc.animate({ 1037 scrollTop: headingOffsets[index] - scrollOffset - 10 1038 }, 300, 'swing', function() { 1039 1040 1041 if (isScrollSpy && linksArr) { 1042 $(linksArr).removeClass('active'); 1043 $(linksArr[index]).addClass('active'); 1044 } 1045 1046 setTimeout(function() { 1047 navScrolling = false;
1048 }, 300); 1049 }); 1050 } 1051 1052 // link click handlers 1053 $tocList.on('click', '.mkb-anchors-list__item-link', function (e) { 1054 var $item = $(e.currentTarget); 1055 1056 if ($item.attr('href') !== '#') { 1057 return; // normal link, just navigate 1058 } 1059 1060 var index = parseInt($item.data('index'), 10); 1061 1062 e.preventDefault(); 1063 1064 if (isScrollSpy) { 1065 $links.removeClass('active'); 1066 $item.addClass('active'); 1067 } 1068 1069 navigateToChapter(index); 1070 }); 1071 1072 var chapterHash = window.location.hash && window.location.hash.replace(/^#/, ''); 1073 1074 if (chapterHash && chapterHash.indexOf('ch_') !== -1) { 1075 var chapterIndex = parseInt(chapterHash.replace(/^ch_/, '')) - 1; 1076 1077 if (chapterIndex && chapterIndex > 0) { 1078 setTimeout(function() { 1079 navigateToChapter(chapterIndex); 1080 }, 300); 1081 } else if (isScrollSpy && chapterIndex === 0) { 1082 $(linksArr[0]).addClass('active'); 1083 } 1084 } else if (isScrollSpy) { 1085 $(linksArr[0]).addClass('active'); 1086 } 1087 1088 if (!isScrollSpy) { 1089 return; 1090 } 1091 1092 // ScrollSpy 1093 var win = window; 1094 var doc = document.documentElement; 1095 1096 $(win).on('scroll', throttle(150, function() { 1097 if (navScrolling) { return; } 1098 1099 var top = win.pageYOffset || doc.scrollTop; 1100 1101 linksArr.forEach(function(item, index) { 1102 var curr = index === 0 ? 0 : headingOffsets[index] - scrollOffset - 1; 1103 var next = (index === headingOffsets.length - 1 ? 9999999 : headingOffsets[index + 1]) - scrollOffset - 1; 1104 1105 $(item).toggleClass('active', top >= curr && top < next); 1106 }); 1107 })); 1108 } 1109 1110 /** 1111 * Wraps headings n back to top containers when necessary 1112 * @param $headings 1113 */ 1114 function wrapTOCAnchors($headings) { 1115 $headings.each(function(index, el) { 1116 var $heading = $(el); 1117 1118 $heading.wrap('<div class="mkb-anchor mkb-clearfix mkb-back-to-top-' + 1119 settings['back_to_top_position'] + 1120 '"></div>'); 1121 1122 $heading.addClass('mkb-anchor__title'); 1123 1124 $heading.parent().append('<a href="#" class="mkb-back-to-top" title="' + 1125 settings['back_to_top_text'] + '">' + 1126 settings['back_to_top_text'] + 1127 ( 1128 settings['show_back_to_top_icon'] ? 1129 '<i class="mkb-back-to-top-icon fa ' + settings['back_to_top_icon'] + '"></i>' : 1130 '' 1131 ) + 1132 '</a>'); 1133 }); 1134 } 1135 1136 /** 1137 * Articles fancy box 1138 */ 1139 function initArticlesFancyBox() { 1140 if (!$.fn.fancybox) { 1141 return; 1142 } 1143 1144 // with captions 1145 $('figure[id^="attachment"] a').each(function (index, item) { 1146 var $item = $(item); 1147 var text = $item.parent().find('.wp-caption-text').text(); 1148 1149 $item.fancybox({ 1150 titlePosition: 'over', 1151 title: text 1152 }); 1153 }); 1154 1155 // no captions 1156 $('.mkb-single-content img').each(function(i, img) { 1157 var $img = $(img); 1158 var $link = $img.parent(); 1159 1160 if ($img.parents('figure.wp-caption').length || !$link.attr('href')) { 1161 return; 1162 } 1163 1164 $link.fancybox({ 1165 titlePosition: 'none', 1166 title: '' 1167 }); 1168 }); 1169 } 1170 1171 /** 1172 * Search clear 1173 * @param e 1174 */ 1175 function handleSearchClear(e) { 1176 e.preventDefault(); 1177 1178 $(e.currentTarget) 1179 .parents('.kb-search__input-wrap') 1180 .find('.kb-search__input') 1181 .val('') 1182 .trigger('input') 1183 .focus(); 1184 } 1185 1186 function initSearchInputs() { 1187 $kbSearch.each(function (index, el) { 1188 var $search = $(el); 1189 1190 var searchHandler = searchMode === 'blocking' ? 1191 debounce(onSearchType.bind(this, $search), parseInt(settings['search_delay'], 10) || 1000, false) : 1192 throttle(parseInt(settings['search_delay'], 10) || 300, true, onSearchType.bind(this, $search), true); 1193 1194 $search.on('input', searchHandler); 1195 1196 if (settings['live_search_prevent_submit']) { 1197 $search.parents('form.kb-search__form').on('submit', function(e) { 1198 e.preventDefault(); 1199 }); 1200 } 1201 }); 1202 } 1203 1204 /** 1205 * Detects if live search disabled for current platform 1206 * @returns {boolean} 1207 */ 1208 function isSearchDisabled() { 1209 return Boolean(settings['live_search_disable_' + platform]); 1210 } 1211 1212 /** 1213 * Sticky sidebar 1214 */ 1215 function setupArticleStickySidebar() { 1216 1217 if (settings['single_template'] === 'theme' ||
1218 !settings['article_sidebar_sticky'] || 1219 !info.isSingle || 1220 platform !== 'desktop' || 1221 window.outerWidth < parseInt(settings['article_sidebar_sticky_min_width']['size'], 10)) { 1222 1223 // sticky sidebar not enabled or not allowed 1224 return; 1225 } 1226 1227 var sticky = false; 1228 var sidebarPosition = settings['article_sidebar']; 1229 var atBottom = false; 1230 var $sidebar = $('.mkb-sidebar'); 1231 var sidebarHeight = $sidebar.outerHeight(); 1232 var $root = $sidebar.parents('.mkb-root'); 1233 var rootHeight = $root.outerHeight(); 1234 var rootHeightInner = $root.height(); 1235 var rootPad = rootHeight - rootHeightInner; 1236 var rootTop = $root.offset().top; 1237 var triggerPos = rootTop - rootPad / 2; 1238 var winHeight = window.innerHeight; 1239 var bottomOffset = winHeight > sidebarHeight + rootPad ? winHeight - sidebarHeight - rootPad : 0; 1240 var width = $sidebar.outerWidth(); 1241 var win = window; 1242 var doc = document.documentElement; 1243 1244 function recalculateMetrics() { 1245 rootHeight = $root.outerHeight(); 1246 rootHeightInner = $root.height(); 1247 rootPad = rootHeight - rootHeightInner; 1248 rootTop = $root.offset().top; 1249 triggerPos = rootTop - rootPad / 2; 1250 winHeight = window.innerHeight; 1251 sidebarHeight = $sidebar.outerHeight(); 1252 bottomOffset = winHeight > sidebarHeight + rootPad ? winHeight - sidebarHeight - rootPad : 0; 1253 } 1254 1255 $(win).on('load', recalculateMetrics); 1256 $(win).on('resize', debounce(recalculateMetrics, 1000)); 1257 1258 setInterval(recalculateMetrics, 500); // sometimes content height changes dynamically 1259 1260 function updateSidebarLeftPosition() { 1261 var left = $root.get(0).getBoundingClientRect().left; 1262 $sidebar.css('left', left + parseInt($root.css('padding-left')) + 'px'); 1263 } 1264 1265 function handleScroll() { 1266 var top = win.pageYOffset || doc.scrollTop; 1267 var bottom = top + winHeight - bottomOffset; 1268 1269 if (bottom > rootHeight + rootTop && !atBottom || bottom <= rootHeight + rootTop && atBottom) { 1270 atBottom = !atBottom; 1271 $sidebar.toggleClass('mkb-fixed-bottom', atBottom); 1272 1273 if (sidebarPosition === 'left') { 1274 if (atBottom) { 1275 $sidebar.css('left', ''); 1276 } else { 1277 updateSidebarLeftPosition(); 1278 } 1279 } 1280 } 1281 1282 if (sticky && top >= triggerPos || !sticky && top < triggerPos) { 1283 return; 1284 } 1285 1286 sticky = !sticky; 1287 $sidebar.toggleClass('mkb-fixed', sticky); 1288 $sidebar.css('max-width', sticky ? width + 'px' : 'none'); 1289 1290 if (sidebarPosition === 'left') { 1291 if (sticky) { 1292 if (!atBottom) { 1293 updateSidebarLeftPosition(); 1294 } 1295 1296 $(window).on('resize', updateSidebarLeftPosition); 1297 } else { 1298 $sidebar.css('left', 0); 1299 $(window).off('resize', updateSidebarLeftPosition); 1300 } 1301 } 1302 } 1303 1304 $(win).on('scroll', handleScroll); 1305 } 1306 1307 /** 1308 * FAQ 1309 */ 1310 function setupFaq() { 1311 var $faqContainer = $('.fn-kb-faq-container'); 1312 1313 if (!$faqContainer.length) { 1314 return; 1315 } 1316 1317 function getHashFaqId() { 1318 var currentHash = window.location.hash.replace('#', ''); 1319 return /^qa_/.test(currentHash) && currentHash.replace('qa_', '') || false; 1320 } 1321 1322 function setHashFaq(id) { 1323 history.replaceState(null, '', 1324 window.location.origin + window.location.pathname + window.location.search + 1325 '#qa_' + id); 1326 } 1327 1328 function clearHash() { 1329 history.replaceState(null, '', 1330 window.location.origin + window.location.pathname + window.location.search); 1331 } 1332 1333 $faqContainer.each(function(index, item) { 1334 var $container = $(item); 1335 var FAQ_HIDDEN_CLASS = 'mkb-faq-item-hidden'; 1336 var FAQ_SECTION_HIDDEN_CLASS = 'mkb-faq-section-hidden'; 1337 var $filterForm = $container.find('.fn-kb-faq-filter'); 1338 var $filter = $filterForm.find('.fn-kb-faq-filter-input'); 1339 var $sections = $container.find('.fn-kb-faq-section'); 1340 var $noResults = $container.find('.fn-kb-faq-no-results'); 1341 1342 // FAQ sections 1343 var sections = [].map.call($sections, function(section) { 1344 var $section = $(section); 1345 var $count = $section.find('.fn-kb-faq-section-count'); 1346 var $items = [].map.call($section.find('.fn-kb-faq-item'), function(item) { 1347 var $item = $(item); 1348 1349 return { 1350 $el: $item, 1351 question: $item.find('.fn-kb-faq-question').text().trim().toLowerCase(), 1352 answer: $item.find('.fn-kb-faq-answer').text().trim().toLowerCase(), 1353 isVisible: true 1354 }; 1355 }); 1356 1357 return { 1358 $el: $section, 1359 $countEl: $count.length ? $count : null, 1360 items: $items, 1361 visible: [].map.call($items, function(item) { return $(item) }), 1362 hidden: [] 1363 }; 1364 }); 1365 1366 /** 1367 * FAQ Filter 1368 */ 1369 var currentFilter; 1370 1371 function updateFilter(visCheck) { 1372 visCheck = visCheck || checkVisibility; 1373 1374 var totalVisible = 0; 1375 1376 // check visibility 1377 sections.forEach(function(section) { 1378 section.visible = []; 1379 section.hidden = [];
1380 section.items.forEach(function(item) { 1381 section[(item.isVisible = visCheck(item)) ? 'visible' : 'hidden'].push(item); 1382 }); 1383 totalVisible += section.visible.length; 1384 }); 1385 1386 $noResults.toggleClass('mkb-hidden', totalVisible > 0); 1387 1388 sections.forEach(function(section) { 1389 section.visible.forEach(function(item) { 1390 item.$el.removeClass(FAQ_HIDDEN_CLASS); 1391 }); 1392 section.hidden.forEach(function(item) { 1393 item.$el.addClass(FAQ_HIDDEN_CLASS); 1394 }); 1395 section.$countEl && section.$countEl.html(section.visible.length + 1396 ' ' + (section.visible.length === 1 ? i18n['question'] : i18n['questions']) 1397 ); 1398 section.$el.toggleClass(FAQ_SECTION_HIDDEN_CLASS, !section.visible.length); 1399 section.visible = []; 1400 section.hidden = []; 1401 }); 1402 1403 if (settings['faq_filter_open_single'] && totalVisible === 1) { 1404 var $onlyItem = $container.find('.fn-kb-faq-item:not(.' + FAQ_HIDDEN_CLASS + ')'); 1405 1406 if ($onlyItem.length && !$onlyItem.hasClass('kb-faq__questions-list-item--open')) { 1407 toggleAnswer($onlyItem); 1408 } 1409 } 1410 } 1411 1412 var resetFilter = updateFilter.bind(this, function() { return true; }); 1413 1414 function checkVisibility(item) { 1415 return item.question.indexOf(currentFilter) !== -1 || item.answer.indexOf(currentFilter) !== -1; 1416 } 1417 1418 function handleFilterChange(e) { 1419 var needle = (e.currentTarget.value || '').trim(); 1420 1421 if (needle.length < 3) { 1422 $filterForm.addClass('kb-faq__filter--empty'); 1423 currentFilter = ''; 1424 resetFilter(); 1425 1426 $filter.focus(); 1427 return 1428 } 1429 1430 $filterForm.removeClass('kb-faq__filter--empty'); 1431 currentFilter = needle.toLowerCase(); 1432 updateFilter(); 1433 } 1434 1435 function handleFilterClear(e) { 1436 e.preventDefault(); 1437 1438 $filter.val('').trigger('input'); 1439 } 1440 1441 if ($filterForm.length) { 1442 $container.on('input', '.fn-kb-faq-filter-input', handleFilterChange); 1443 $container.on('click', '.fn-kb-faq-filter-clear', handleFilterClear); 1444 } 1445 1446 var OPEN_SPEED = settings['faq_slow_animation'] ? 400 : 100; 1447 1448 function getMaxHeight(el) { 1449 return Array.prototype.reduce.call(el.childNodes, function(store, current) { 1450 return store + (current.offsetHeight || 0); 1451 }, 0); 1452 } 1453 1454 /** 1455 * FAQ Toggle 1456 */ 1457 function toggleAnswer($item, customToggleSpeed) { 1458 customToggleSpeed = customToggleSpeed || OPEN_SPEED; 1459 1460 var $answer = $item.find('.fn-kb-faq-answer'); 1461 var $link = $item.find('.fn-kb-faq-link'); 1462 var answerEl = $answer.get(0); 1463 var maxHeight = getMaxHeight(answerEl); 1464 1465 if ($item.hasClass('kb-faq__questions-list-item--open')) { 1466 $answer.css('max-height', maxHeight); 1467 $answer.animate({maxHeight: 0}, customToggleSpeed, 'swing', function() { 1468 $item.removeClass('kb-faq__questions-list-item--open'); 1469 }); 1470 1471 if (settings['faq_url_update']) { 1472 var hashFaqId = getHashFaqId(); 1473 1474 if (hashFaqId && hashFaqId == $link.data('id')) { 1475 clearHash(); 1476 } 1477 } 1478 } else { 1479 if (settings['faq_toggle_mode']) { 1480 sections.forEach(function(section) {
1481 section.items.forEach(function(item) { 1482 closeAnswer(item.$el, customToggleSpeed); 1483 }); 1484 }); 1485 } 1486 1487 $answer.animate({maxHeight: maxHeight}, customToggleSpeed, 'swing', function() { 1488 $answer.css('max-height', 'none'); 1489 $item.addClass('kb-faq__questions-list-item--open'); 1490 }); 1491 1492 if (settings['faq_url_update']) { 1493 setHashFaq($link.data('id')); 1494 } 1495 } 1496 } 1497 1498 function openAnswer($item) { 1499 var $answer = $item.find('.fn-kb-faq-answer'); 1500 var answerEl = $answer.get(0); 1501 var maxHeight = getMaxHeight(answerEl); 1502 1503 if (!$item.hasClass('kb-faq__questions-list-item--open')) { 1504 $answer.animate({maxHeight: maxHeight}, OPEN_SPEED, 'swing', function() { 1505 $answer.css('max-height', 'none'); 1506 $item.addClass('kb-faq__questions-list-item--open'); 1507 }); 1508 } 1509 } 1510 1511 function closeAnswer($item, customCloseSpeed) { 1512 customCloseSpeed = customCloseSpeed || OPEN_SPEED; 1513 1514 var $answer = $item.find('.fn-kb-faq-answer'); 1515 var answerEl = $answer.get(0); 1516 var maxHeight = getMaxHeight(answerEl); 1517 1518 if ($item.hasClass('kb-faq__questions-list-item--open')) { 1519 $answer.css('max-height', maxHeight); 1520 $answer.animate({maxHeight: 0}, customCloseSpeed, 'swing', function() { 1521 $item.removeClass('kb-faq__questions-list-item--open'); 1522 }); 1523 } 1524 } 1525 1526 var trackedQuestionViews = {}; 1527 1528 function handleQuestionClick (e) { 1529 e.preventDefault(); 1530 1531 var $link = $(e.currentTarget); 1532 var id = Number($link.data('id')); 1533 var $item = $link.parent(); 1534 var isOpen = $item.hasClass('kb-faq__questions-list-item--open'); 1535 1536 toggleAnswer($item); 1537 1538 if (!isOpen && id && !isNaN(id) && !trackedQuestionViews[id]) { 1539 trackQuestionView(id); 1540 trackedQuestionViews[id] = true; 1541 } 1542 } 1543 1544 function trackQuestionView(id) { 1545 $.ajax({ 1546 method: 'POST', 1547 url: ajaxUrl, 1548 dataType: 'json', 1549 data: addAjaxNonce({ 1550 action: 'mkb_faq_view', 1551 id: id 1552 }) 1553 }); 1554 } 1555 1556 function handleToggleAllClick (e) { 1557 e.preventDefault(); 1558 1559 var $link = $(e.currentTarget); 1560 var isOpen = $link.hasClass('kb-faq__toggle-all-link--open'); 1561 1562 sections.forEach(function(section) { 1563 section.items.forEach(function(item) { 1564 isOpen ? closeAnswer(item.$el) : openAnswer(item.$el); 1565 }); 1566 }); 1567 1568 $link.toggleClass('kb-faq__toggle-all-link--open'); 1569 } 1570 1571 $container.on('click', '.fn-kb-faq-link', handleQuestionClick); 1572 $container.on('click', '.fn-kb-faq-toggle-all', handleToggleAllClick); 1573 1574 // global API 1575 $body.on('minerva_faq_toggle_answer_' + index, function(options) { 1576 var id = options.id; 1577 1578 var $link = $container.find('.fn-kb-faq-link[data-id="' + id + '"]'); 1579 1580 if (!$link.length) { 1581 return; 1582 } 1583 1584 var $item = $link.parent(); 1585 1586 if (!$item.hasClass('kb-faq__questions-list-item--open')) { 1587 toggleAnswer($item, 100); 1588 } 1589 }) 1590 }); 1591 1592 if (settings['faq_url_update']) { 1593 // navigate to faq from hash 1594 var hashFaqId = getHashFaqId(); 1595 var $faqLink = hashFaqId && $('.fn-kb-faq-link[data-id="' + hashFaqId + '"]'); 1596 var scrollOffset = parseInt(settings['faq_scroll_offset']['size']); 1597 1598 if ($faqLink && $faqLink.length) { 1599 $faqLink.trigger('click'); 1600 1601 setTimeout(function() { 1602 $doc.animate({ 1603 scrollTop: $faqLink.offset().top - scrollOffset 1604 }, 300); 1605 }, 800); 1606 } 1607 } 1608 } 1609 1610 /** 1611 * Content Tree 1612 */ 1613 function setupContentTreeWidgets() { 1614 var $contentTree = $('.mkb-widget-content-tree__list'); 1615 var openActiveBranch = settings['content_tree_widget_open_active_branch']; 1616 1617 function setListMaxHeight(index, list) { 1618 var $list = $(list); 1619 $list.animate({'max-height': list.scrollHeight}, 200, function() { 1620 $list.css('max-height', 'none'); 1621 }); 1622 } 1623 1624 $contentTree.each(function(index, tree) { 1625 var $tree = $(tree); 1626 var $topics = $tree.find('.mkb-widget-content-tree__topic'); 1627 1628 $tree.on('click', '.mkb-widget-content-tree__topic-name', function(e) { 1629 var topicName = e.currentTarget; 1630 var $topic = $(topicName).parent(); 1631 var topic = $topic.get(0); 1632 1633 if ($topic.hasClass('topic-open')) { 1634 $topic.removeClass('topic-open'); 1635 $topic.find('>ul').css('max-height', '0'); 1636 } else { 1637 var activeBranch = [topic.dataset.id]; 1638 var $parents = $topic.parents('.mkb-widget-content-tree__topic'); 1639 1640 $parents.each(function(index, parent) { 1641 activeBranch.push(parent.dataset.id); 1642 }); 1643 1644 // hide topics that are not in current branch 1645 $topics.each(function(index, item) { 1646 if (activeBranch.indexOf(item.dataset.id) !== -1) { 1647 return; 1648 } 1649 1650 var $item = $(item); 1651 1652 $item.removeClass('topic-open'); 1653 $item.find('>ul').css('max-height', '0'); 1654 }); 1655 1656 $topic.find('>ul').each(setListMaxHeight); 1657 $topic.addClass('topic-open'); 1658 1659 $parents.each(function(index, item) { 1660 var $parentTopic = $(item); 1661 1662 if ($parentTopic.hasClass('topic-open')) { 1663 return; 1664 } 1665 1666 $parentTopic.find('>ul').each(setListMaxHeight); 1667 $parentTopic.addClass('topic-open'); 1668 }); 1669 } 1670 1671 e.preventDefault(); 1672 e.stopImmediatePropagation(); 1673 }); 1674 1675 if (openActiveBranch) { 1676 setTimeout(function() { 1677 $tree.find('.mkb-widget-content-tree__article--active') 1678 .closest('.mkb-widget-content-tree__topic') 1679 .find('>.mkb-widget-content-tree__topic-name') 1680 .trigger('click'); 1681 }, 300); 1682 } 1683 }); 1684 } 1685 1686 /** 1687 * Client submission form 1688 */ 1689 function setupSubmissionForm() { 1690 var $submisionContainers = $('.js-mkb-client-submission'); 1691 1692 $submisionContainers.each(function(index, container) { 1693 var $container = $(container); 1694 var $form = $container.find('.js-mkb-client-submission-form'); 1695 1696 var $title = $container.find('.js-mkb-submission-title'); 1697 var $topic = $container.find('.js-mkb-submission-topic'); 1698 var $content = $container.find('#mkb-client-editor'); 1699 var $antispamAnswer = $container.find('.js-mkb-real-human-answer'); 1700 var $messagesContainer = $container.find('.js-mkb-form-messages'); 1701 1702 var toolbarOptions = [ 1703 [{ 'header': [1, 2, 3, 4, 5, 6, false] }], 1704 ['bold', 'italic', 'underline', 'strike'], 1705 [{ 'list': 'ordered'}, { 'list': 'bullet' }], 1706 ['link'], 1707 ['blockquote', 'code-block'], 1708 ['clean'] 1709 ]; 1710 1711 var quillEditor = new Quill('#mkb-client-editor', { 1712 modules: { 1713 toolbar: toolbarOptions, 1714 history: { 1715 delay: 2000,
1716 maxStack: 500, 1717 userOnly: true 1718 } 1719 }, 1720 theme: settings.submit_content_editor_skin 1721 }); 1722 1723 function getData() { 1724 return { 1725 action: 'mkb_save_client_submission', 1726 title: $title.val().trim(), 1727 content: $content.find('.ql-editor').get(0).innerHTML.trim(), 1728 topic: $topic.length ? $topic.val().trim() : '', 1729 antispam: $antispamAnswer.length ? $antispamAnswer.val().trim() : '' 1730 } 1731 } 1732 1733 function validateData(data) { 1734 var status = 0; 1735 var errors = []; 1736 1737 if (!data.title) { 1738 status = 1; 1739 errors.push(i18n.submission_empty_title); 1740 } 1741 1742 if (!data.content 1743 .replace('<p>', '') 1744 .replace('</p>', '') 1745 .replace('<br>', '')) { 1746 1747 status = 1; 1748 errors.push(i18n.submission_empty_content); 1749 } 1750 1751 if (!data.antispam && $antispamAnswer.length) { 1752 status = 1; 1753 errors.push(settings.antispam_failed_message); 1754 } 1755 1756 return { 1757 status: status, 1758 errors: errors 1759 } 1760 } 1761 1762 function renderMessages(messages) { 1763 $messagesContainer.html(messages.reduce(function(html, err) { 1764 return html + '<div class="mkb-form-message">' + err + '</div>' 1765 }), ''); 1766 } 1767 1768 function showMessages(messageClass, noScroll) { 1769 noScroll = noScroll || false; 1770 1771 $messagesContainer.addClass(messageClass).removeClass('mkb-hidden'); 1772 1773 if (!noScroll) { 1774 $doc.animate({ 1775 scrollTop: $messagesContainer.offset().top - adminOffset - parseInt(settings['global_scroll_offset']['size'], 10) 1776 }, 100); 1777 } 1778 } 1779 1780 function showErrorMessages(noScroll) { 1781 showMessages('mkb-form-error', noScroll); 1782 } 1783 1784 function showSuccessMessages(noScroll) { 1785 showMessages('mkb-form-success', noScroll); 1786 } 1787 1788 $container.on('click', '.js-mkb-client-submission-send', function(e) { 1789 var requestData = getData(); 1790 var $btn = $(e.currentTarget); 1791 1792 if ($btn.hasClass('mkb-disabled')) { 1793 return; 1794 } 1795 1796 var validationResult = validateData(requestData); 1797 1798 if (validationResult.status !== 0) { 1799 renderMessages(validationResult.errors); 1800 showErrorMessages(); 1801 1802 $doc.animate({ 1803 scrollTop: $messagesContainer.offset().top - adminOffset - parseInt(settings['global_scroll_offset']['size'], 10) 1804 }, 100); 1805 1806 return; 1807 } 1808 1809 $btn.addClass('mkb-disabled'); 1810 1811 $.ajax({ 1812 method: 'POST', 1813 url: ajaxUrl, 1814 dataType: 'json', 1815 data: addAjaxNonce(requestData) 1816 }).done(function(response) { 1817 1818 $btn.removeClass('mkb-disabled'); 1819 1820 if (response.status == 1) { 1821 renderMessages([response.error]); 1822 showErrorMessages(); 1823 } else { 1824 renderMessages([settings.submit_success_message]); 1825 showSuccessMessages(true); 1826 1827 $form.html(''); 1828 1829 $doc.animate({ 1830 scrollTop: $container.offset().top - adminOffset - parseInt(settings['global_scroll_offset']['size'], 10) 1831 }, 100); 1832 } 1833 }); 1834 }); 1835 }); 1836 } 1837 1838 /** 1839 * KB floating helper 1840 */ 1841 function setupHelper() { 1842 var $helper = $('.js-mkb-floating-helper'); 1843 1844 if (!$helper.length) { 1845 return; 1846 } 1847 1848 var $searchInput = $helper.find('.kb-search__input'); 1849 1850 $helper.on('click', '.js-mkb-floating-helper-btn', function() { 1851 $helper.addClass('mkb-floating-helper-wrap--open'); 1852 $searchInput.get(0).focus(); 1853 }); 1854 1855 $helper.on('click', '.js-mkb-floating-helper-close', function() { 1856 $helper.removeClass('mkb-floating-helper-wrap--open'); 1857 }); 1858 1859 if (settings.fh_display_mode === 'auto') { 1860 setTimeout(function() { 1861 $helper.addClass('mkb-floating-helper-wrap--ready'); 1862 }, settings.fh_show_delay); 1863 } else if (settings.fh_display_mode === 'js_click') { 1864 $('body').on('click', '.js-mkb-helper-open', function() { 1865 $helper.addClass('mkb-floating-helper-wrap--ready'); 1866 }); 1867 } 1868 } 1869 1870 /** 1871 * Attachments 1872 */ 1873 function setupAttachments() { 1874 // download tracking 1875 $body.on('click', '.js-mkb-attachment-link', function(e) { 1876 var link = e.currentTarget; 1877 var id = link.dataset.id; 1878 1879 if (!id) { 1880 return; 1881 } 1882 1883 $.ajax({ 1884 method: 'POST', 1885 url: ajaxUrl, 1886 dataType: 'json', 1887 data: addAjaxNonce({ 1888 action: 'mkb_track_attachment_download', 1889 id: id 1890 }) 1891 }) 1892 }); 1893 } 1894 1895 /** 1896 * GLOSSARY TOOLTIPS 1897 */ 1898 1899 /** 1900 * Glossary helper 1901 * @param char 1902 * @returns {boolean} 1903 */ 1904 function isDelimiter(char) { 1905 return [',', '.', ':', ';', '"', '\''].includes(char) || /\s/.test(char); 1906 } 1907 1908 /** 1909 * Glossary helper 1910 * @param matchedText 1911 * @param wholeText 1912 * @param offset 1913 * @returns {boolean} 1914 */ 1915 function checkMatchDelimiters(matchedText, wholeText, offset) {
1916 var validBefore = true; 1917 var validAfter = true; 1918 1919 // check before char 1920 if (offset > 0) { 1921 validBefore = isDelimiter(wholeText[offset - 1]); 1922 } 1923 1924 // check after char 1925 if (offset < wholeText.length - matchedText.length) { 1926 validAfter = isDelimiter(wholeText[offset + matchedText.length]); 1927 } 1928 1929 return validBefore && validAfter; 1930 } 1931 1932 /** 1933 * Match text helper for glossary 1934 * @param node 1935 * @param regex 1936 * @param callback 1937 * @param excludeElements 1938 * @returns {*} 1939 */ 1940 var glossaryHighlightLimit = parseInt(settings.glossary_highlight_limit, 10); 1941 var glossaryHighlightCache = {}; 1942 var glossaryExcludeElements = [ 1943 'script', 1944 'style', 1945 'iframe', 1946 'canvas', 1947 'h1', 1948 'h2', 1949 'h3', 1950 'h4', 1951 'h5', 1952 'h6' 1953 ]; 1954 1955 glossaryHighlightLimit = !isNaN(glossaryHighlightLimit) && glossaryHighlightLimit > 0 1956 ? glossaryHighlightLimit 1957 : 0; 1958 1959 /** 1960 * 1961 * @param node 1962 * @param regex {RegExp} 1963 * @param termId {Number} 1964 * @param createWrappedText {Function} 1965 * @returns {*} 1966 */ 1967 function matchGlossaryText(node, regex, termId, createWrappedText) { 1968 if (glossaryHighlightLimit && glossaryHighlightCache[termId] >= glossaryHighlightLimit) { 1969 return; 1970 } 1971 1972 var child = node.firstChild; 1973 1974 while (child) { 1975 switch (child.nodeType) { 1976 case 1: // An Element node like <p> or <div>. 1977 if (glossaryExcludeElements.indexOf(child.tagName.toLowerCase()) > -1) { 1978 break; 1979 } 1980 1981 // note, recursion here, actual replace only in text nodes 1982 matchGlossaryText(child, regex, termId, createWrappedText); 1983 break; 1984 1985 case 3: // The actual Text inside an Element. 1986 var bk = 0; // position back offset 1987 1988 child.data.replace(regex, function glossaryTextNodeReplacerFn(matchedText) { 1989 // NOTE: we need this additional check here, because replace may match multiple substrings 1990 // in a single text node 1991 if (glossaryHighlightLimit && glossaryHighlightCache[termId] >= glossaryHighlightLimit) { 1992 return; 1993 } 1994 1995 var args = [].slice.call(arguments); 1996 var offset = args[args.length - 2]; 1997 var wholeText = args[args.length - 1]; 1998 1999 if (!checkMatchDelimiters(matchedText, wholeText, offset)) { 2000 return; // TODO: why different return type? maybe matchedText 2001 } 2002 2003 var newTextNode = child.splitText(offset + bk), tag; 2004 2005 bk -= child.data.length + matchedText.length; 2006 2007 newTextNode.data = newTextNode.data.substr(matchedText.length); 2008 2009 tag = createWrappedText.apply(window, [child].concat(args)); 2010 2011 child.parentNode.insertBefore(tag, newTextNode); 2012 child = newTextNode; 2013 }); 2014 regex.lastIndex = 0; 2015 break; 2016 } 2017 2018 child = child.nextSibling; 2019 } 2020 2021 return node; 2022 } 2023 2024 /** 2025 * Glossary tooltips 2026 */ 2027 function setupGlossaryTooltips() { 2028 if (platform !== 'desktop' && settings['glossary_mobile_mode'] === 'none') { 2029 return; 2030 } 2031 2032 if (info.postId && settings['glossary_highlight_exclude_ids'].includes(String(info.postId))) { 2033 return; 2034 } 2035 2036 var selectors = ['.js-mkb-glossary-content-wrap']; 2037 2038 if (settings['glossary_highlight_post_types'].includes('product')) { 2039 selectors.push('.woocommerce-product-details__short-description'); 2040 } 2041 2042 if (settings['glossary_highlight_post_types'].includes('page') && info.isPage) { 2043 selectors.push('.elementor'); 2044 } 2045 2046 var selector = selectors.join(', '); 2047 var $container = $(selector); 2048 2049 if (!$container.length) { 2050 return; 2051 } 2052 2053 var glossaryList = window.MinervaKB.glossary || []; 2054 2055 if (!glossaryList.length) { 2056 return; 2057 } 2058 2059 var glossaryById = glossaryList.reduce(function(glossary, current) { 2060 glossary[current.id] = current; 2061 return glossary; 2062 }, {}); 2063 2064 glossaryList.forEach(function(glossaryItem) { 2065 var synonyms = (glossaryItem.synonyms || '') 2066 .split(',') 2067 .map(function(s) { 2068 return s.trim().toLowerCase(); 2069 }) 2070 .filter(Boolean); 2071 var synonymPart = synonyms.length ? synonyms.join('|') + '|' : ''; 2072 var glossaryTermRE = new RegExp(synonymPart + glossaryItem.title.toLowerCase(), 'gi'); 2073 2074 glossaryHighlightCache[glossaryItem.id] = 0; 2075 2076 $container.each(function(index, glossarySection) { 2077 matchGlossaryText(glossarySection, glossaryTermRE, glossaryItem.id, function(node, match, offset) { 2078 var wrappedEl = document.createElement(platform === 'desktop' ? 'span': 'a'); 2079 2080 wrappedEl.className = "mkb-glossary-term js-mkb-glossary-term"; 2081 wrappedEl.textContent = match; 2082 wrappedEl.dataset.id = glossaryItem.id; 2083 2084 if (platform !== 'desktop') { 2085 wrappedEl.setAttribute('href', glossaryItem.permalink); 2086 2087 if (settings['glossary_mobile_mode'] === 'link_new') { 2088 wrappedEl.setAttribute('target', '_blank'); 2089 } 2090 } 2091 2092 if (glossaryHighlightLimit) { 2093 ++glossaryHighlightCache[glossaryItem.id]; 2094 } 2095 2096 return wrappedEl; 2097 }); 2098 }); 2099 }); 2100 2101 function showTooltip(e) { 2102 e.preventDefault(); 2103 2104 var el = e.currentTarget; 2105 var isDesktop = platform === 'desktop'; 2106 var termRect = el.getBoundingClientRect(); 2107 var leftOffset = termRect.left + el.offsetWidth / 2; 2108 var rightOffset = document.documentElement.clientWidth - termRect.left - el.offsetWidth / 2; 2109 var tooltipHalfWidth = 160; // half of tooltip, hardcoded 2110 var isLeftCropped = isDesktop && leftOffset < tooltipHalfWidth; 2111 var isRightCropped = isDesktop && rightOffset < tooltipHalfWidth; 2112 var id = el.dataset.id; 2113 var loadingHTML = '<div class="mkb-glossary-loader"><i class="fa ' + settings['glossary_loader_icon'] + ' fa-spin fa-fw"></i></div>'; 2114 var content = glossaryById[id].html; 2115 var $tooltipInner = $( 2116 '<div class="js-mkb-glossary-tooltip mkb-glossary-tooltip' + 2117 (isLeftCropped ? ' mkb-glossary-tooltip--left' : '') + 2118 (isRightCropped ? ' mkb-glossary-tooltip--right' : '') + 2119 '">' + 2120 (platform !== 'desktop' ? 2121 '<a href="#" class="js-mkb-glossary-tooltip-close mkb-glossary-tooltip-close"><i class="fa fa-times"></i></a>' : 2122 '') + 2123 '<div class="js-mkb-glossary-tooltip-inner mkb-glossary-tooltip-inner">' + 2124 (content || loadingHTML) + 2125 '</div>' + 2126 '</div>' 2127 ); 2128 2129 $(el).append($tooltipInner); 2130 2131 if (!content) { 2132 $.ajax({ 2133 method: 'GET', 2134 url: ajaxUrl, 2135 dataType: 'json', 2136 data: addAjaxNonce({ 2137 action: 'mkb_get_glossary_term_content', 2138 id: id 2139 }) 2140 }).done(function(response) { 2141 if (response && response.html) { 2142 glossaryById[id].html = response.html; 2143 2144 $tooltipInner.find('.js-mkb-glossary-tooltip-inner').html(glossaryById[id].html); 2145 } 2146 }); 2147 } 2148 2149 if (platform !== 'desktop') { 2150 $body.addClass('mkb-scroll-lock'); 2151 } 2152 2153 setTimeout(function() { 2154 $tooltipInner.addClass('state--animated'); 2155 }, 10); 2156 } 2157 2158 function removeTooltip(e) { 2159 var el = e.currentTarget; 2160 2161 $(el).find('.js-mkb-glossary-tooltip').remove(); 2162 } 2163 2164 function removeTooltipOnClose(e) { 2165 e.preventDefault(); 2166 e.stopImmediatePropagation(); 2167 2168 var el = e.currentTarget; 2169 2170 $body.removeClass('mkb-scroll-lock'); 2171 2172 $(el).parents('.js-mkb-glossary-tooltip').remove(); 2173 } 2174 2175 if (platform === 'desktop') { 2176 $('.js-mkb-glossary-term').hover(showTooltip, removeTooltip); 2177 } else if (settings['glossary_mobile_mode'] === 'popup') { 2178 $body.on('click', '.js-mkb-glossary-term', showTooltip); 2179 $body.on('click', '.js-mkb-glossary-term .js-mkb-glossary-tooltip-close', removeTooltipOnClose); 2180 } 2181 } 2182 2183 function setupGlossary() { 2184 var $glossaryContainer = $('.js-mkb-glossary-list'); 2185 2186 if (!$glossaryContainer.length) { 2187 return; 2188 } 2189 2190 $glossaryContainer.each(function(wrapIndex, item) { 2191 var $wrap = $(item); 2192 2193 $wrap.on('click', '.js-mkb-glossary-list-toc a', function(e) { 2194 e.preventDefault(); 2195 e.stopImmediatePropagation(); 2196 2197 var link = e.currentTarget; 2198 2199 $doc.animate({ 2200 scrollTop: $(link.getAttribute('href')).offset().top - parseInt(settings['glossary_scroll_offset']['size'], 10) 2201 }, 300); 2202 }); 2203 }); 2204 } 2205 2206 /** 2207 * Simple check. Main check is on server side 2208 */ 2209 function checkIfNeedToPassReCaptcha($form) { 2210 $responseControl = $form.find('.g-recaptcha-response'); 2211 2212 if (!$responseControl.length || $responseControl.val()) { 2213 return false;
2214 } 2215 2216 return true; 2217 } 2218 2219 /** 2220 * Login Form 2221 */ 2222 function setupLoginForm() { 2223 var $loginForm = $('.js-mkb-support-account-login-form'); 2224 2225 if (!$loginForm.length) { 2226 return; 2227 } 2228 2229 var loginForm = new ui.Form($loginForm.get(0), {}); 2230 2231 $loginForm.on('submit', function(e) { 2232 e.preventDefault(); 2233 2234 if (loginForm.isLoading()) { 2235 return; 2236 } 2237 2238 if (checkIfNeedToPassReCaptcha($loginForm)) { 2239 alert(i18n['recaptcha_user_message']); 2240 2241 return; 2242 } 2243 2244 var formData = loginForm.serialize(); 2245 2246 loginForm.clearMessages(); 2247 loginForm.startLoading(); 2248 2249 $.ajax({ 2250 method: 'POST', 2251 url: ajaxUrl, 2252 dataType: 'json', 2253 data: addAjaxNonce(formData) 2254 }).done(function(response) { 2255 if (response.status == 1) { 2256 loginForm.showMessages(response.error || i18n['form_error_general_text'], 'error'); 2257 } else { 2258 window.location.reload(); 2259 } 2260 }).always(function() { 2261 loginForm.endLoading(); 2262 }); 2263 }); 2264 } 2265 2266 /** 2267 * Register Support Account 2268 */ 2269 function setupRegisterForm() { 2270 var $createSupportAccountForm = $('.js-mkb-create-support-account-form'); 2271 2272 if (!$createSupportAccountForm.length) { 2273 return; 2274 } 2275 2276 var createSupportAccountForm = new ui.Form($createSupportAccountForm.get(0), {}); 2277 2278 $createSupportAccountForm.on('submit', function(e) { 2279 e.preventDefault(); 2280 2281 if (createSupportAccountForm.isLoading()) { 2282 return; 2283 } 2284 2285 if (checkIfNeedToPassReCaptcha($createSupportAccountForm)) { 2286 alert(i18n['recaptcha_user_message']); 2287 2288 return; 2289 } 2290 2291 var formData = createSupportAccountForm.serialize(); 2292 2293 createSupportAccountForm.clearMessages(); 2294 createSupportAccountForm.startLoading(); 2295 2296 $.ajax({ 2297 method: 'POST', 2298 url: ajaxUrl, 2299 dataType: 'json', 2300 data: addAjaxNonce(formData) 2301 }).done(function(response) { 2302 if (response.status == 1) { 2303 // TODO: process errors 2304 createSupportAccountForm.showMessages(response.error || i18n['form_error_general_text'], 'error'); 2305 } else { 2306 if (response.message) { 2307 createSupportAccountForm.showMessages(response.message, 'success'); 2308 } 2309 2310 if (response.loggedIn) { 2311 window.location.reload(); 2312 } 2313 } 2314 }).always(function() { 2315 createSupportAccountForm.endLoading(); 2316 }); 2317 }); 2318 } 2319 2320 /** 2321 * Create user/guest ticket form 2322 */ 2323 function setupCreateTicketForm() { 2324 var $createTicketForm = $('.js-mkb-create-ticket'); 2325 2326 if (!$createTicketForm.length) { 2327 return; 2328 } 2329 2330 var $ticketMessageEl = $createTicketForm.find('.js-mkb-ticket-message'); 2331 var quillEditor = ui.setupQuillEditor({ 2332 $form: $createTicketForm, 2333 editorSelector: '.js-mkb-ticket-message', 2334 placeholder: $ticketMessageEl.data('placeholder'), 2335 theme: settings.submit_content_editor_skin, 2336 fileUpload: info['userCanAttachFiles'] 2337 }); 2338 2339 var createTicketForm = new ui.Form($createTicketForm.get(0), { 2340 quillEditors: [quillEditor] 2341 }); 2342 2343 $createTicketForm.on('submit', function(e) { 2344 e.preventDefault(); 2345 2346 if (createTicketForm.isLoading()) { 2347 return; 2348 } 2349 2350 if (checkIfNeedToPassReCaptcha($createTicketForm)) { 2351 alert(i18n['recaptcha_user_message']); 2352 2353 return; 2354 } 2355 2356 var formData = new FormData(this); 2357 var replyAsText = quillEditor.getText().trim(); 2358 var isGuest = formData.get('action') === 'mkb_create_guest_support_ticket'; 2359 2360 if (!replyAsText) { 2361 return alert(i18n['ticket_create_error_empty_message_text']); 2362 } 2363 2364 formData.append('message', $createTicketForm.find('.ql-editor').get(0).innerHTML.trim()); 2365 2366 createTicketForm.clearMessages(); 2367 createTicketForm.startLoading(); 2368 2369 $.ajax({ 2370 url: ajaxUrl, 2371 method: 'POST', 2372 contentType: false, 2373 processData: false, 2374 data: addAjaxNonceToFormData(formData) 2375 }).done(function(response) { 2376 if (typeof response === 'string') { 2377 // fix for response with file upload 2378 response = JSON.parse(response); 2379 } 2380 2381 if (response.status == 1) { 2382 // TODO: errors 2383 createTicketForm.showMessages(response.error || i18n['form_error_general_text'], 'error'); 2384 } else { 2385 if (isGuest) { 2386 var $response = $( 2387 '<div class="mkb-form-server-response status--success">' + 2388 '<div class="mkb-create-ticket-response__icon"><i class="fa fa-check"></i></div>' + 2389 '<div class="mkb-create-ticket-response__heading">' + 2390 i18n['ticket_create_success_message_heading_text'] + 2391 '</div>' + 2392 '<div class="mkb-create-ticket-response__top-message">' + 2393 (formData.get('email') ? 2394 (response.emailSent ? 2395 i18n['ticket_create_success_email_sent_text'] : 2396 i18n['ticket_create_success_email_not_sent_text'] 2397 ) + ' ' : 2398 '') + 2399 i18n['ticket_create_success_link_message_text'] + 2400 '</div>' + 2401 '<div class="mkb-create-ticket-response__link-wrap"><a href="' + response.ticketUrl + '" target="_blank">' + formData.get('title') + '</a></div>' + 2402 '</div>' 2403 ); 2404 2405 $createTicketForm.after($response); 2406 2407 $createTicketForm.remove(); 2408 2409 $doc.animate({ 2410 scrollTop: $response.offset().top - parseInt(settings['global_scroll_offset']['size']) 2411 }, 300); 2412 } else { 2413 window.location.href = response.ticketUrl; 2414 } 2415 } 2416 }).always(function() { 2417 createTicketForm.endLoading(); 2418 }); 2419 }); 2420 2421 // admin area setup
2422 var $otherUserSelect = $('.js-mkb-ticket-other-user-select'); 2423 2424 if ($otherUserSelect.length) { 2425 $otherUserSelect.select2(); 2426 2427 // TODO: remove this hack after updating to next stable Select2 version 2428 $(document).on('select2:open', function() { 2429 document.querySelector(".select2-container--open .select2-search__field").focus() 2430 }); 2431 } 2432 } 2433 2434 /** 2435 * Reply to ticket 2436 */ 2437 function setupReplyToTicketForm() { 2438 var $replyToTicketForm = $('.js-mkb-reply-to-ticket'); 2439 2440 if (!$replyToTicketForm.length) { 2441 return; 2442 } 2443 2444 if (window.sessionStorage.getItem('mkbTicketNewReplyAdded')) { 2445 setTimeout(function() { 2446 $doc.animate({ 2447 scrollTop: $('.js-mkb-ticket-discussion').offset().top - parseInt(settings['global_scroll_offset']['size']) 2448 }, 300); 2449 }, 1000); 2450 2451 window.sessionStorage.removeItem('mkbTicketNewReplyAdded'); 2452 } 2453 2454 // TODO: scroll top maybe 2455 var quillEditor = ui.setupQuillEditor({ 2456 $form: $replyToTicketForm, 2457 editorSelector: '.js-mkb-ticket-reply-content', 2458 placeholder: i18n['ticket_reply_field_placeholder_text'], 2459 theme: settings.submit_content_editor_skin, 2460 fileUpload: info['userCanAttachFiles'] 2461 }); 2462 2463 var replyForm = new ui.Form($replyToTicketForm.get(0), { 2464 quillEditors: [quillEditor] 2465 }); 2466 2467 $replyToTicketForm.on('submit', function(e) { 2468 e.preventDefault(); 2469 2470 if (replyForm.isLoading()) { 2471 return; 2472 } 2473 2474 // TODO: maybe move formData with files to Form as well 2475 var formData = new FormData(this); 2476 var replyAsText = quillEditor.getText().trim(); 2477 2478 if (!replyAsText) { 2479 replyForm.showMessages(i18n['ticket_reply_error_empty_text'], 'error'); 2480 return; 2481 } 2482 2483 formData.append('reply', $replyToTicketForm.find('.ql-editor').get(0).innerHTML.trim()); 2484 formData.append('action', 'mkb_reply_to_ticket'); 2485 2486 replyForm.clearMessages(); 2487 replyForm.startLoading(); 2488 2489 $.ajax({ 2490 url: ajaxUrl, 2491 method: 'POST', 2492 contentType: false, 2493 processData: false, 2494 data: addAjaxNonceToFormData(formData) 2495 }).done(function(response) { 2496 if (typeof response === 'string') { 2497 // fix for response with file upload 2498 response = JSON.parse(response); 2499 } 2500 2501 if (response.status == 1) { 2502 replyForm.showMessages(i18n['form_error_general_text'], 'error'); 2503 } else { 2504 var message = i18n['ticket_reply_success_message_text']; 2505 2506 if (response.fileUploadErrors && response.fileUploadErrors.length) { 2507 message += ' ' + i18n['ticket_reply_error_files_not_added_text']; 2508 } 2509 2510 replyForm.showMessages(message, 'success'); 2511 2512 window.sessionStorage.setItem('mkbTicketNewReplyAdded', true); 2513 2514 window.location.reload(); 2515 } 2516 }).always(function() { 2517 replyForm.endLoading(); 2518 }); 2519 }); 2520 } 2521 2522 /** 2523 * Reopen ticket 2524 */ 2525 function setupReopenTicketForm() { 2526 var $reopenTicketForm = $('.js-mkb-reopen-ticket-form'); 2527 2528 if (!$reopenTicketForm.length) { 2529 return; 2530 } 2531 2532 var reopenTicketForm = new ui.Form($reopenTicketForm.get(0), {}); 2533 2534 $reopenTicketForm.on('submit', function(e) { 2535 e.preventDefault(); 2536 2537 if (reopenTicketForm.isLoading()) { 2538 return; 2539 } 2540 2541 var formData = reopenTicketForm.serialize(); 2542 2543 formData.action = 'mkb_reopen_ticket'; 2544 2545 reopenTicketForm.clearMessages(); 2546 reopenTicketForm.startLoading(); 2547 2548 $.ajax({ 2549 url: ajaxUrl, 2550 method: 'POST', 2551 data: addAjaxNonce(formData) 2552 }).done(function(response) { 2553 if (response.status == 1) { 2554 // TODO: errors 2555 reopenTicketForm.showMessages(i18n['form_error_general_text'], 'error'); 2556 } else { 2557 reopenTicketForm.showMessages(i18n['ticket_reopen_success_message_text'], 'success'); 2558 2559 window.location.reload(); 2560 } 2561 }).always(function() { 2562 reopenTicketForm.endLoading(); 2563 }); 2564 }); 2565 } 2566 2567 /** 2568 * Ticket credentials 2569 */ 2570 function setupTicketCredentialsForm() { 2571 var $credentialsForm = $('.js-mkb-provide-ticket-credentials'); 2572 2573 if (!$credentialsForm.length) { 2574 return; 2575 } 2576 2577 var credentialsForm = new ui.Form($credentialsForm.get(0), {}); 2578 2579 $credentialsForm.on('click', '.js-mkb-credentials-show', function(e) { 2580 e.preventDefault(); 2581 2582 $credentialsForm.addClass('state--open'); 2583 }); 2584 2585 $credentialsForm.on('click', '.js-mkb-credentials-hide', function(e) { 2586 e.preventDefault(); 2587 2588 $credentialsForm.removeClass('state--open'); 2589 }); 2590 2591 /** 2592 * Provide credentials 2593 */ 2594 $credentialsForm.on('submit', function(e) { 2595 e.preventDefault(); 2596 2597 if (credentialsForm.isLoading()) { 2598 return; 2599 } 2600 2601 var formData = credentialsForm.serialize(); 2602 2603 formData.ticket_credentials = formData.ticket_credentials && formData.ticket_credentials.trim() || ''; 2604 2605 if (!formData.ticket_credentials) { 2606 credentialsForm.showMessages(i18n['ticket_credentials_error_empty_text'], 'error'); 2607 2608 return; 2609 } 2610 2611 formData.action = 'mkb_provide_ticket_credentials'; 2612 2613 credentialsForm.clearMessages(); 2614 credentialsForm.startLoading(); 2615 2616 $.ajax({ 2617 url: ajaxUrl, 2618 method: 'POST', 2619 data: addAjaxNonce(formData) 2620 }).done(function(response) { 2621 if (response.status == 1) { 2622 // TODO: errors 2623 credentialsForm.showMessages(i18n['form_error_general_text'], 'error'); 2624 } else { 2625 credentialsForm.showMessages(i18n['ticket_credentials_s
2625aved_text'], 'success'); 2626 2627 $('.js-mkb-delete-ticket-credentials').removeClass('mkb-hidden'); 2628 } 2629 }).always(function() { 2630 credentialsForm.endLoading(); 2631 }); 2632 }); 2633 2634 /** 2635 * Delete credentials 2636 */ 2637 $('.js-mkb-delete-ticket-credentials').on('click', function(e) { 2638 e.preventDefault(); 2639 2640 if (credentialsForm.isLoading()) { 2641 return; 2642 } 2643 2644 var $btn = $(e.currentTarget); 2645 var formData = credentialsForm.serialize(); 2646 2647 formData.action = 'mkb_delete_ticket_credentials'; 2648 2649 credentialsForm.clearMessages(); 2650 credentialsForm.startLoading(); 2651 2652 $.ajax({ 2653 url: ajaxUrl, 2654 method: 'POST', 2655 data: addAjaxNonce(formData) 2656 }).done(function(response) { 2657 if (response.status == 1) { 2658 // TODO: errors 2659 credentialsForm.showMessages(i18n['form_error_general_text'], 'error'); 2660 } else { 2661 credentialsForm.showMessages(i18n['ticket_credentials_deleted_text'], 'success'); 2662 2663 $btn.addClass('mkb-hidden'); 2664 $('#ticket_credentials').val(''); 2665 } 2666 }).always(function() { 2667 credentialsForm.endLoading(); 2668 }); 2669 }); 2670 } 2671 2672 /** 2673 * Elapsed time tickers 2674 */ 2675 function setupTicketElapsedTimeTickers() { 2676 if (!window.moment) { 2677 return; 2678 } 2679 2680 var locale = $('html').attr('lang') || window.navigator.userLanguage || window.navigator.language; 2681 moment.locale(locale); 2682 2683 $('.js-mkb-human-readable-time').each(function(index, item) { 2684 var $item = $(item); 2685 var timestamp = item.dataset.timestamp * 1000; 2686 var ONCE_A_MINUTE = 1000 * 60; 2687 2688 $item.html(moment.utc(timestamp).fromNow()); 2689 2690 setInterval(function() { 2691 $item.html(moment(timestamp).fromNow()); 2692 }, ONCE_A_MINUTE); 2693 }); 2694 } 2695 2696 function setTicketViewedFlags() { 2697 $.ajax({ 2698 url: ajaxUrl, 2699 method: 'POST', 2700 data: addAjaxNonce({ 2701 action: 'mkb_ticket_viewed_by_customer', 2702 ticketId: info.postId 2703 }) 2704 }); 2705 } 2706 2707 /** 2708 * 2709 */ 2710 function setupTickets() { 2711 setupLoginForm(); 2712 setupRegisterForm(); 2713 2714 setupCreateTicketForm(); 2715 setupReplyToTicketForm(); 2716 setupTicketCredentialsForm(); 2717 setupReopenTicketForm(); 2718 setupTicketElapsedTimeTickers(); 2719 2720 if (info.isTicket) { 2721 setTicketViewedFlags(); 2722 } 2723 } 2724 2725 function isElementorPreviewScreen() { 2726 if (window.URLSearchParams) { 2727 var urlParams = new URLSearchParams(window.location.search); 2728 return Boolean(urlParams.get('elementor-preview')); 2729 } 2730 2731 return false; 2732 } 2733 2734 function setupFeatureRequests() { 2735 $body.on('click', '.js-mkb-vote-for-feature', function(e) { 2736 e.preventDefault(); 2737 2738 var btn = e.currentTarget; 2739 var $btn = $(btn); 2740 var id = btn.dataset.id; 2741 var $wrap = $btn.parent(); 2742 var $votesCount = $wrap.find('.js-mkb-fr-votes-count'); 2743 var $votesCountText = $wrap.find('.js-mkb-fr-votes-count-text'); 2744 var currentVotes = Number($votesCount.text().trim()); 2745 2746 if ($btn.hasClass('state--pending') || $btn.hasClass('state--voted')) { 2747 return; 2748 } 2749 2750 $btn.addClass('state--pending'); 2751 2752 $.ajax({ 2753 method: 'POST', 2754 url: ajaxUrl, 2755 dataType: 'json', 2756 data: addAjaxNonce({ 2757 action: 'mkb_feature_vote', 2758 id: id 2759 }) 2760 }).done(function() { 2761 ++currentVotes; 2762 2763 $votesCount.html(currentVotes); 2764 $votesCountText.html(currentVotes === 1 ? 'vote' : 'votes'); 2765 2766 $btn.addClass('state--voted'); 2767 }).always(function() { 2768 $btn.removeClass('state--pending'); 2769 }); 2770 }); 2771 } 2772 2773 /** 2774 * Feature Request Submit form 2775 */
2776 function setupFeatureRequestSubmitForm() { 2777 var $featureRequestSubmitForm = $('.js-mkb-feature-request-submit-form'); 2778 2779 if (!$featureRequestSubmitForm.length) { 2780 return; 2781 } 2782 2783 var featureRequestSubmitForm = new ui.Form($featureRequestSubmitForm.get(0)); 2784 2785 $featureRequestSubmitForm.on('submit', function(e) { 2786 e.preventDefault(); 2787 2788 if (featureRequestSubmitForm.isLoading()) { 2789 return; 2790 } 2791 2792 if (checkIfNeedToPassReCaptcha($featureRequestSubmitForm)) { 2793 alert(i18n['recaptcha_user_message']); 2794 2795 return; 2796 } 2797 2798 var formData = featureRequestSubmitForm.serialize(); 2799 2800 featureRequestSubmitForm.clearMessages(); 2801 featureRequestSubmitForm.startLoading(); 2802 2803 $.ajax({ 2804 url: ajaxUrl, 2805 method: 'POST', 2806 data: addAjaxNonce(formData) 2807 }).done(function(response) { 2808 if (response.status == 1) { 2809 // TODO: errors 2810 featureRequestSubmitForm.showMessages(response.error || i18n['form_error_general_text'], 'error'); 2811 } else { 2812 featureRequestSubmitForm.showMessages(i18n['feature_request_submit_success_message'], 'success'); 2813 2814 setTimeout(function() { 2815 featureRequestSubmitForm.lock(); 2816 }, 0); 2817 2818 $doc.animate({ 2819 scrollTop: $featureRequestSubmitForm.offset().top - parseInt(settings['global_scroll_offset']['size']) 2820 }, 300); 2821 } 2822 }).always(function() { 2823 featureRequestSubmitForm.endLoading(); 2824 }).fail(function() { 2825 featureRequestSubmitForm.showMessages(i18n['form_error_general_text'], 'error'); 2826 }); 2827 }); 2828 } 2829 2830 /** 2831 * Main plugin startup 2832 */ 2833 function init() { 2834 // for future use 2835 var isElementorPreview = isElementorPreviewScreen(); 2836 2837 $adminBar = $('#wpadminbar'); 2838 adminOffset = $adminBar.length ? $adminBar.height() : 0; 2839 2840 if ($kbSearch.length && !isSearchDisabled()) { 2841 initSearchInputs(); 2842 $body.on('keydown', onSearchKeyPress); 2843 $body.on('click', '.kb-search__clear', handleSearchClear); 2844 focusInput(); 2845 onSearchType($kbSearch.eq(0)); // restore previous search 2846 2847 setupExtraSearchGroupsOnPageTargets(); 2848 } 2849 2850 // FAQ items 2851 setupFaq(); 2852 2853 // Glossary items 2854 setupGlossary(); 2855 2856 // Feature Requests 2857 setupFeatureRequests(); 2858 setupFeatureRequestSubmitForm(); 2859 2860 // article related code 2861 if (info.isSingle) { 2862 $body.on('click', '.mkb-article-extra__like', handleArticleLike); 2863 $body.on('click', '.mkb-article-extra__dislike', handleArticleDislike); 2864 $body.on('click', '.mkb-article-extra__feedback-form-submit', handleFeedbackSubmit); 2865 $body.on('input', '.mkb-article-extra__feedback-form-message-area', handleFeedbackType); 2866 2867 handleArticleBackToTop(); 2868 handleArticleTOC(); 2869 2870 if (settings['article_fancybox']) { 2871 initArticlesFancyBox(); 2872 } 2873 2874 trackArticleView(); 2875 2876 // sticky articles sidebar 2877 setupArticleStickySidebar(); 2878 2879 setupAttachments(); 2880 } 2881 2882 setupGlossaryTooltips(); 2883 setupContentTreeWidgets(); 2884 setupSubmissionForm(); 2885 setupHelper(); 2886 2887 // todo: on ticket page only 2888 setupTickets(); 2889 } 2890 2891 // start 2892 $(document).ready(init); 2893 2894})(window.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.