1( function( $, publicConfig ) { 2 'use strict'; 3 4 let JetReviews = { 5 6 eventBus: new Vue(), 7 8 initedInstance: [], 9 10 captchaToken: false, 11 12 init: function() { 13 JetReviews.defineVueComponents(); 14 15 this.initInstances(); 16 17 window.addEventListener( 'jetReviews/editor/templateRenderer/renderSuccess', ( event ) => { 18 this.initBlockInstances(); 19 }, false ); 20 }, 21 22 initInstances: function() { 23 let instancesList = $( '.jet-reviews-advanced' ); 24 25 if ( ! instancesList[0] ) { 26 return false; 27 } 28 29 instancesList.each( function() { 30 let $target = $( this ), 31 instanceId = $target.attr( 'id' ), 32 uniqid = $target.data( 'uniqid' ) || false, 33 options = window['jetReviewsWidget' + uniqid] || false; 34 35 if ( ! options ) { 36 let optionsScript = document.getElementById( 'jetReviewsWidgetOptions' + uniqid ); 37 38 if ( optionsScript ) { 39 eval( optionsScript.innerHTML ); 40 options = window['jetReviewsWidget' + uniqid] || false; 41 } 42 } 43 44 if ( ! $target[0] || ! uniqid || ! options ) { 45 return; 46 } 47 48 window.JetReviews.createJetReviewAdvancedInstance( instanceId, options ); 49 } ); 50 }, 51 52 initBlockInstances: function() { 53 const targetNode = document.querySelector( '.jet-reviews-block-holder' ); 54 55 if ( ! targetNode ) return; 56 57 const observer = new MutationObserver( function ( mutationsList, observer ) { 58 59 for ( const mutation of mutationsList ) { 60 61 if ( mutation.type === 'childList' ) { 62 const event = new Event( 'jetReviews/editor/block/renderSuccess' ); 63 64 document.dispatchEvent( event ); 65 observer.disconnect(); 66 } 67 } 68 } ); 69 70 observer.observe( targetNode, { childList: true, subtree: true } ); 71 72 document.addEventListener( 'jetReviews/editor/block/renderSuccess', ( event ) => { 73 this.initInstances(); 74 } ); 75 }, 76 77 getRecaptchaConfig: function() { 78 return window.jetReviewPublicConfig && window.jetReviewPublicConfig.recaptchaConfig ? window.jetReviewPublicConfig.recaptchaConfig : {}; 79 }, 80 81 isRecaptchaEnabled: function( recaptchaConfig ) { 82 return recaptchaConfig && ( true === recaptchaConfig.enable || 'true' === recaptchaConfig.enable || '1' === recaptchaConfig.enable || 1 === recaptchaConfig.enable ); 83 }, 84 85 isRecaptchaReady: function( recaptchaConfig ) { 86 return this.isRecaptchaEnabled( recaptchaConfig ) && 87 '' !== ( recaptchaConfig.site_key || '' ) && 88 window.grecaptcha && 89 'function' === typeof window.grecaptcha.ready && 90 'function' === typeof window.grecaptcha.execute; 91 }, 92 93 getCaptchaValidationMessage: function( message = '' ) { 94 if ( '' !== message ) { 95 return message; 96 } 97 98 if ( window.jetReviewPublicConfig && window.jetReviewPublicConfig.labels && window.jetReviewPublicConfig.labels.captchaValidationFailed ) { 99 return window.jetReviewPublicConfig.labels.captchaValidationFailed; 100 } 101 102 if ( window.jetReviewPublicConfig && window.jetReviewPublicConfig.captchaValidationFailed ) { 103 return window.jetReviewPublicConfig.captchaValidationFailed; 104 } 105 106 return '*Captcha validation failed'; 107 }, 108 109 executeRecaptcha: function( action, onSuccess, onError ) { 110 let recaptchaConfig = this.getRecaptchaConfig(), 111 handleError = function() { 112 if ( 'function' === typeof onError ) { 113 onError(); 114 } 115 }; 116 117 if ( ! this.isRecaptchaEnabled( recaptchaConfig ) ) { 118 return false; 119 } 120 121 if ( ! this.isRecaptchaReady( recaptchaConfig ) ) { 122 handleError(); 123 124 return true; 125 } 126 127 window.grecaptcha.ready( function() { 128 try { 129 window.grecaptcha.execute( 130 recaptchaConfig.site_key, 131 { 132 action: action 133 } 134 ).then( function( token ) { 135 JetReviews.captchaToken = token; 136 137 if ( 'function' === typeof onSuccess ) { 138 onSuccess( token ); 139 } 140 } ).catch( handleError ); 141 } catch ( error ) { 142 handleError(); 143 } 144 } ); 145 146 return true; 147 }, 148 149 defineVueComponents: function() { 150 151 Vue.component( 'jet-reviews-widget-pagination', { 152 template: '#jet-reviews-widget-pagination-template', 153 props: { 154 current: { 155 type: Number, 156 default: 1 157 }, 158 total: { 159 type: Number, 160 default: 0 161 }, 162 pageSize: { 163 type: Number, 164 default: 10 165 }, 166 prevIcon: { 167 type: String, 168 default: '<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
168<path d="M5.67089 0L-5.96046e-08 6L5.67089 12L7 10.5938L2.65823 6L7 1.40625L5.67089 0Z"/></svg>' 169 }, 170 nextIcon: { 171 type: String, 172 default: '<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.32911 0L7 6L1.32911 12L0 10.5938L4.34177 6L0 1.40625L1.32911 0Z"/></svg>' 173 }, 174 customCss: { 175 type: String, 176 default: '' 177 }, 178 }, 179 data() { 180 return { 181 baseClass: 'jet-reviews-widget-pagination', 182 currentPage: this.current, 183 currentPageSize: this.pageSize 184 }; 185 }, 186 187 watch: { 188 total ( val ) { 189 let maxPage = Math.ceil( val / this.currentPageSize ); 190 191 if ( maxPage < this.currentPage ) { 192 this.currentPage = ( maxPage === 0 ? 1 : maxPage ); 193 } 194 }, 195 current ( val ) { 196 this.currentPage = val; 197 }, 198 pageSize ( val ) { 199 this.currentPageSize = val; 200 } 201 }, 202 203 computed: { 204 classesList() { 205 206 let classesList = [ 207 this.baseClass, 208 ]; 209 210 if ( this.customCss ) { 211 classesList.push( this.customCss ); 212 } 213 214 return classesList; 215 }, 216 217 prevClasses () { 218 return [ 219 `${this.baseClass}__item`, 220 `${this.baseClass}__item--prev`, 221 { 222 [`${this.baseClass}__item--disabled`]: this.currentPage === 1 || false 223 } 224 ]; 225 }, 226 227 nextClasses () { 228 return [ 229 `${this.baseClass}__item`, 230 `${this.baseClass}__item--next`, 231 { 232 [`${this.baseClass}__item--disabled`]: this.currentPage === this.allPages || false 233 } 234 ]; 235 }, 236 237 firstPageClasses () { 238 return [ 239 `${this.baseClass}__item`, 240 { 241 [`${this.baseClass}__item--active`]: this.currentPage === 1 242 } 243 ]; 244 }, 245 246 lastPageClasses () { 247 return [ 248 `${this.baseClass}__item`, 249 { 250 [`${this.baseClass}__item--active`]: this.currentPage === this.allPages 251 } 252 ]; 253 }, 254 255 allPages () { 256 const allPage = Math.ceil( this.total / this.currentPageSize ); 257 258 return ( allPage === 0 ) ? 1 : allPage; 259 }, 260 }, 261 methods: { 262 changePage ( page ) { 263 264 if ( this.currentPage !== page ) { 265 266 this.currentPage = page; 267 this.$emit( 'update:current', page ); 268 this.$emit( 'on-change', page ); 269 } 270 }, 271 272 prev () { 273 const current = this.currentPage; 274 275 if ( current <= 1 ) { 276 return false; 277 } 278 279 this.changePage( current - 1 ); 280 }, 281 282 next () { 283 const current = this.currentPage; 284 285 if ( current >= this.allPages ) { 286 return false; 287 } 288 289 this.changePage(current + 1); 290 }, 291 292 fastPrev () { 293 const page = this.currentPage - 5; 294 295 if ( page > 0 ) { 296 this.changePage( page ); 297 } else { 298 this.changePage( 1 ); 299 } 300 }, 301 302 fastNext () { 303 const page = this.currentPage + 5; 304 305 if ( page > this.allPages ) { 306 this.changePage( this.allPages ); 307 } else { 308 this.changePage( page ); 309 } 310 }, 311 }, 312 313 } ); 314 315 Vue.component( 'jet-advanced-reviews-form', { 316 template: '#jet-advanced-reviews-form-template', 317 318 props: { 319 reviewFields: Array 320 }, 321 322 data: function() { 323 return ( { 324 reviewSubmiting: false, 325 reviewTitle: '', 326 reviewContent: '', 327 reviewAuthorName: '', 328 reviewAuthorMail: '', 329 reviewMedia: [], 330 reviewMediaPreview: [], 331 messageText: '', 332 fields: this.reviewFields, 333 isValidUpload: true, 334 trySend: false 335 } ); 336 }, 337 338 mounted: function() { 339 let self = this; 340 341 Vue.nextTick().then( function () { 342 let reviewContent = self.$refs.reviewContent, 343 textarea = reviewContent.$refs.textarea; 344 345 textarea.focus(); 346 } ); 347 348 if ( this.$root.isUserGuest ) { 349 this.reviewAuthorName = JetReviews.getLocalStorageData( 'guestName', '' ); 350 this.reviewAuthorMail = JetReviews.getLocalStorageData( 'guestMail', '' ); 351 } 352 }, 353 354 computed: { 355 formControlsVisible: function() { 356 357 if ( this.$root.isUserGuest ) { 358 return this.isValidReviewContent && 359 this.isValidReviewTitle && 360 this.isValidAuthorName && 361 this.isValidAuthorEmail; 362 } 363 364 return this.isValidReviewContent && this.isValidReviewTitle; 365 366 }, 367 368 formMessageVisible: function() { 369 return '' !== this.messageText; 370 }, 371 372 reviewTitleVisible: function() { 373 return this.$root.options.reviewTitleInputVisible; 374 }, 375 376 reviewContentVisible: function() { 377 return this.$root.options.reviewContentInputVisible; 378 }, 379 380 isValidReviewTitle: function() { 381 382 if ( ! this.trySend ) { 383 return true 384 } 385 386 return '' !== this.reviewTitle || ! this.reviewTitleVisible; 387 }, 388 389 isValidReviewContent: function() { 390 391 if ( ! this.trySend ) { 392 return true 393 } 394 395 return '' !== this.reviewContent || ! this.reviewContentVisible; 396 }, 397 398 isValidAuthorName: function() { 399 400 if ( ! this.trySend ) { 401 return true 402 } 403 404 return '' !== this.reviewAuthorName; 405 }, 406 407 isValidAuthorEmail: function() { 408 409 if ( ! this.trySend ) { 410 return true 411 } 412 413 return ( '' !== this.reviewAuthorMail && JetReviews.checkValidEmail( this.reviewAuthorMail ) ); 414 }, 415 416 isValidMedia: function() { 417 return this.isValidUpload; 418 } 419 420 }, 421 422 methods: { 423 cancelSubmit: function() { 424 JetReviews.eventBus.$emit( 'closeNewReviewForm', { uniqId: this.$root.options.uniqId } ); 425 }, 426 427 submitReview: function() { 428 this.trySend = true; 429 430 if ( ! this.formControlsVisible ) { 431 return false;
432 } 433 434 let self = this, 435 forSendingData = { 436 source: this.$root.sourceData.source, 437 source_type: this.$root.sourceData.sourceType, 438 source_id: this.$root.sourceData.sourceId, 439 title: this.reviewTitle, 440 content: this.reviewContent, 441 author_id: this.$root.userData.id, 442 author_name: this.reviewAuthorName, 443 author_mail: this.reviewAuthorMail, 444 rating_data: this.fields, 445 media: this.reviewMedia, 446 }; 447 448 // Recaptcha enable check 449 if ( JetReviews.executeRecaptcha( 450 'submit_review', 451 function( token ) { 452 let modifyData = Object.assign( {}, forSendingData, { 453 captcha_token: token 454 } ); 455 456 self.submitReviewHandle( modifyData ); 457 }, 458 function() { 459 self.messageText = JetReviews.getCaptchaValidationMessage( self.$root.options.labels.captchaValidationFailed ); 460 } 461 ) ) { 462 return false; 463 } 464 465 this.submitReviewHandle( forSendingData ); 466 }, 467 468 submitReviewHandle: function( sendData = false ) { 469 let self = this; 470 471 if ( ! sendData ) { 472 console.warn( 'Empty new review data for sending' ); 473 474 return false; 475 } 476 477 const formData = new FormData(); 478 479 formData.append( 'source', sendData.source ); 480 formData.append( 'source_type', sendData.source_type ); 481 formData.append( 'source_id', sendData.source_id ) ; 482 formData.append( 'title', sendData.title ); 483 formData.append( 'content', sendData.content ); 484 formData.append( 'author_id', sendData.author_id ); 485 formData.append( 'author_name', sendData.author_name ); 486 formData.append( 'author_mail', sendData.author_mail ); 487 formData.append( 'rating_data', JSON.stringify( sendData.rating_data || {} ) ); 488 formData.append( 'captcha_token', sendData.captcha_token || '' ); 489 490 for ( const file of sendData.media ) { 491 formData.append( 'attached_media[]', file ); 492 } 493 494 this.reviewSubmiting = true; 495 this.messageText = ''; 496 497 wp.apiFetch( { 498 method: 'post', 499 path: window.jetReviewPublicConfig.submitReviewRoute, 500 body: formData, 501 } ).then( function( response ) { 502 response = self.maybeModifyResponce( response ); 503 504 let responseSuccess = response.success, 505 responseData = response.data, 506 responseMessage = response.message; 507 508 self.reviewSubmiting = false; 509 self.messageText = responseMessage; 510 511 if ( self.$root.isUserGuest ) { 512 let guestReviewedItems = JetReviews.getLocalStorageData( self.$root.guestReviewedStorageName, [] ); 513 514 if ( responseSuccess && ! guestReviewedItems.includes( self.$root.sourceData.sourceId ) ) { 515 self.$root.userData.name = self.reviewAuthorName; 516 self.$root.userData.mail = self.reviewAuthorMail; 517 JetReviews.setLocalStorageData( 'guestName', self.reviewAuthorName ); 518 JetReviews.setLocalStorageData( 'guestMail', self.reviewAuthorMail ); 519 520 guestReviewedItems.push( self.$root.sourceData.sourceId ); 521 JetReviews.setLocalStorageData( self.$root.guestReviewedStorageName, guestReviewedItems ); 522 } 523 } 524 525 if ( responseSuccess ) { 526 if ( 'review-created' === response.code ) { 527 JetReviews.eventBus.$emit( 'addReview', { 528 uniqId: self.$root.options.uniqId, 529 reviewData: responseData.item, 530 } ); 531 532 self.$root.reviewsAverageRating = +responseData.rating; 533 } 534 535 self.$root.formVisible = false; 536 self.$root.userData.canReview.allowed = false; 537 self.$root.userData.canReview.message = responseMessage; 538 } 539 } ).catch( function( error ) { 540 self.reviewSubmiting = false; 541 self.messageText = error && error.message ? error.message : self.$root.userData.canReview.message; 542 } ); 543 }, 544 545 maybeModifyResponce: function( responce = false ) { 546 let code = responce.code; 547 548 if ( 'need-approve' === code && '' !== this.$root.options.labels.moderatorCheckMessage ) { 549 responce.message = this.$root.options.labels.moderatorCheckMessage; 550 } 551 552 if ( 'already-created' === code && '' !== this.$root.options.labels.alreadyReviewedMessage ) { 553 responce.message = this.$root.options.labels.alreadyReviewedMessage; 554 } 555 556 return responce; 557 }, 558 559 handleFiles( files ) { 560 this.reviewMedia = files; 561 }, 562 563 handleUploadStatus( payload ) { 564 this.isValidUpload = payload; 565 }, 566 567 requiredFieldsCheck() { 568 569 return true; 570 } 571 572 } 573 }); 574 575 Vue.component( 'slider-input', {
576 template: '#jet-advanced-reviews-slider-input-template', 577 578 props: { 579 value: { 580 type: [ String, Number ], 581 default: '' 582 }, 583 max: { 584 type: [ Number ], 585 default: 5 586 }, 587 step: { 588 type: [ Number ], 589 default: 1 590 }, 591 label: { 592 type: [ String, Boolean ], 593 default: false 594 }, 595 }, 596 597 data: function() { 598 return ( { 599 //currentValue: this.value, 600 } ); 601 }, 602 603 computed: { 604 valueLabel: function() { 605 return this.value; 606 } 607 }, 608 609 methods: { 610 handleInput ( event ) { 611 let value = event.target.value; 612 613 this.$emit( 'input', value ); 614 this.$emit( 'on-change', event ); 615 }, 616 handleChange ( event ) { 617 this.$emit( 'on-input-change', event ); 618 } 619 }, 620 } ); 621 622 Vue.component( 'stars-input', { 623 template: '#jet-advanced-reviews-stars-input-template', 624 625 props: { 626 value: { 627 type: [ String, Number ], 628 default: '' 629 }, 630 max: { 631 type: [ Number ], 632 default: 5 633 }, 634 step: { 635 type: [ Number ], 636 default: 1 637 }, 638 label: { 639 type: [ String, Boolean ], 640 default: false 641 }, 642 }, 643 644 data: function() { 645 return ( { 646 currentRating: this.value, 647 } ); 648 }, 649 650 computed: { 651 valueLabel: function() { 652 return `${ this.value }/${ this.max }`; 653 }, 654 655 rating: function() { 656 return ( this.currentRating / this.max ) * 100; 657 }, 658 659 preparedRating: function() { 660 661 if ( 10 > this.rating ) { 662 return 10; 663 } 664 665 return this.rating; 666 }, 667 668 emptyIcon: function() { 669 return this.$root.refsHtml.emptyStarIcon || '<i class="far fa-star"></i>'; 670 }, 671 672 emptyIcons: function() { 673 let icon = `<div class="jet-reviews-star">${ this.$root.refsHtml.emptyStarIcon }</div>` || '<div class="jet-reviews-star"><i class="far fa-star"></i></div>'; 674 675 return icon.repeat( this.max ); 676 }, 677 678 filledIcons: function() { 679 let icon = `<div class="jet-reviews-star">${ this.$root.refsHtml.filledStarIcon }</div>` || `<div class="jet-reviews-star"><i class="fas fa-star"></i></div>`; 680 return icon.repeat( this.max ); 681 }, 682 683 ratingClass: function() { 684 let ratingClass = 'very-high-rating'; 685 686 if ( this.rating >= 80 && this.rating <= 100 ) { 687 ratingClass = 'very-high-rating'; 688 } 689 690 if ( this.rating >= 60 && this.rating <= 79 ) { 691 ratingClass = 'high-rating'; 692 } 693 694 if ( this.rating >= 40 && this.rating <= 59 ) { 695 ratingClass = 'medium-rating'; 696 } 697 698 if ( this.rating >= 22 && this.rating <= 39 ) { 699 ratingClass = 'low-rating'; 700 } 701 702 if ( this.rating >= 0 && this.rating <= 21 ) { 703 ratingClass = 'very-low-rating'; 704 } 705 706 return ratingClass; 707 }, 708 }, 709 710 methods: { 711 ratingClick( rating ) { 712 this.currentRating = rating; 713 this.$emit( 'input', rating ); 714 }, 715 716 ratingMouseOver( rating ) { 717 this.currentRating = rating; 718 }, 719 720 ratingMouseOut() { 721 this.currentRating = this.value; 722 }, 723 }, 724 }); 725 726 Vue.component( 'jet-advanced-reviews-item', { 727 template: '#jet-advanced-reviews-item-template', 728 729 props: { 730 itemData: Object 731 }, 732 733 data: function() { 734 return ( { 735 commentFormVisible: false, 736 commentText: '', 737 commentAuthorName: '', 738 commentAuthorMail: '', 739 commentSubmiting: false, 740 approvalSubmiting: false, 741 parentComment: 0, 742 commentsVisible: false, 743 responseMessage: '', 744 detailsVisibleState: false 745 } ); 746 }, 747 748 mounted: function() { 749 if ( this.$root.isUserGuest ) { 750 this.commentAuthorName = JetReviews.getLocalStorageData( 'guestName', '' ); 751 this.commentAuthorMail = JetReviews.getLocalStorageData( 'guestMail', '' ); 752 753 let guestReviewRatedData = JetReviews.getLocalStorageData( this.$root.guestRatedStorageName, {} ); 754 755 if ( guestReviewRatedData.hasOwnProperty( this.itemData.id ) ) { 756 this.$set( this.itemData, 'approval', guestReviewRatedData[ this.itemData.id ] ); 757 } 758 759 } 760 }, 761 762 computed: {
763 isDetailsFieldsAvaliable: function() { 764 return 1 < this.itemData.rating_data.length; 765 }, 766 767 detailsVisible: function() { 768 return this.isDetailsFieldsAvaliable && this.detailsVisibleState; 769 }, 770 771 averageRatingVisible: function() { 772 return 'average' === this.$root.options.reviewRatingType; 773 }, 774 775 detailsRatingVisible: function() { 776 return 'details' === this.$root.options.reviewRatingType; 777 }, 778 779 authorVerificationData: function() { 780 return 0 !== this.itemData.verifications.length ? this.itemData.verifications : false; 781 }, 782 783 isCommentsEmpty: function() { 784 return 0 === this.itemData.comments.length; 785 }, 786 787 isCommentsVisible: function() { 788 return !this.isCommentsEmpty && this.commentsVisible; 789 }, 790 791 itemCommentsCount: function() { 792 let itemCommentsCount = false; 793 794 itemCommentsCount = this.getCommentsCount( this.itemData.comments ); 795 796 return itemCommentsCount; 797 }, 798 799 pinnedVisible: function() { 800 return this.itemData.pinned; 801 }, 802 803 commentControlsVisible: function() { 804 805 if ( this.$root.isUserGuest ) { 806 return this.isCommentValid && 807 this.isValidAuthorName && 808 this.isValidAuthorEmail; 809 } 810 811 return this.isCommentValid; 812 }, 813 814 averageRatingData: function() { 815 let ratingDatalength = this.itemData.rating_data.length, 816 summaryValue = 0, 817 avarageValue = 0, 818 summaryMax = 0, 819 avarageMax = 0; 820 821 summaryValue = this.itemData.rating_data.reduce( function( accumulator, currentValue ) { 822 return accumulator + +currentValue.field_value; 823 }, 0 ); 824 825 summaryMax = this.itemData.rating_data.reduce( function( accumulator, currentValue ) { 826 return accumulator + +currentValue.field_max; 827 }, 0 ); 828 829 avarageValue = Math.round( summaryValue / ratingDatalength ); 830 avarageMax = Math.round( summaryMax / ratingDatalength ); 831 832 return { 833 rating: Math.round( avarageValue * 100 / avarageMax, 1 ), 834 max: Math.round( avarageMax, 1 ), 835 value: Math.round( avarageValue, 1 ) 836 }; 837 }, 838 839 addCommentIcon: function() { 840 return this.$root.refsHtml.newCommentButtonIcon || false; 841 }, 842 843 showCommentsIcon: function() { 844 return this.$root.refsHtml.showCommentsButtonIcon || false; 845 }, 846 847 pinnedIcon: function() { 848 return this.$root.refsHtml.pinnedIcon || '<i class="fas fa-thumbtack"></i>'; 849 }, 850 851 likeIcon: function() { 852 let emptyLike = this.$root.refsHtml.reviewEmptyLikeIcon || '<i class="far fa-thumbs-up"></i>', 853 filledLike = this.$root.refsHtml.reviewFilledLikeIcon || '<i class="fas fa-thumbs-up"></i>'; 854 855 return ! this.itemData.approval.like ? emptyLike : filledLike; 856 }, 857 858 dislikeIcon: function() { 859 let emptyDislike = this.$root.refsHtml.reviewEmptyDislikeIcon || '<i class="far fa-thumbs-down"></i>', 860 filledDislike = this.$root.refsHtml.reviewFilledDislikeIcon || '<i class="fas fa-thumbs-down"></i>'; 861 862 return ! this.itemData.approval.dislike ? emptyDislike : filledDislike; 863 }, 864 865 userCanComment: function() { 866 return this.$root.sourceData.commentsAllowed && this.$root.userData.canComment.allowed; 867 }, 868 869 userCanRate: function() { 870 return this.$root.sourceData.approvalAllowed && this.$root.userData.canRate; 871 }, 872 873 isValidAuthorName: function() { 874 return '' !== this.commentAuthorName; 875 }, 876 877 isValidAuthorEmail: function() { 878 return JetReviews.checkValidEmail( this.commentAuthorMail ); 879 }, 880 881 isCommentValid: function() { 882 return '' !== this.commentText; 883 }, 884 885 mediaList: function() { 886 887 if ( 0 === this.itemData.media.length ) { 888 return false 889 } 890 891 return this.itemData.media; 892 } 893 894 }, 895 896 methods: { 897 showCommentForm: function() { 898 let self = this; 899 900 this.commentFormVisible = !this.commentFormVisible; 901 902 if ( this.commentFormVisible ) { 903 Vue.nextTick().then( function () { 904 let commentContent = self.$refs.commentContent, 905 textarea = commentContent.$refs.textarea; 906 907 textarea.focus(); 908 } ); 909 } 910 }, 911 912 cancelNewComment: function() { 913 this.commentFormVisible = false;
914 this.responseMessage = ''; 915 }, 916 917 submitNewComment: function() { 918 919 let self = this, 920 forSendingData = { 921 source: this.$root.sourceData.source, 922 source_type: this.$root.sourceData.sourceType, 923 source_id: this.$root.sourceData.sourceId, 924 parent_id: this.parentComment, 925 review_id: this.itemData.id, 926 author_id: this.$root.userData.id, 927 author_name: this.commentAuthorName, 928 author_mail: this.commentAuthorMail, 929 content: this.commentText, 930 }; 931 932 // Recaptcha enable check 933 if ( JetReviews.executeRecaptcha( 934 'submit_review_comment', 935 function( token ) { 936 let modifyData = Object.assign( {}, forSendingData, { 937 captcha_token: token 938 } ); 939 940 self.submitCommentHandler( modifyData ); 941 }, 942 function() { 943 self.responseMessage = JetReviews.getCaptchaValidationMessage( self.$root.options.labels.captchaValidationFailed ); 944 } 945 ) ) { 946 return false; 947 } 948 949 this.submitCommentHandler( forSendingData ); 950 }, 951 952 submitCommentHandler: function( sendData = false ) { 953 let self = this; 954 955 if ( ! sendData ) { 956 console.warn( 'Empty new comment data for sending' ); 957 958 return false; 959 } 960 961 this.commentSubmiting = true; 962 963 wp.apiFetch( { 964 method: 'post', 965 path: window.jetReviewPublicConfig.submitReviewCommentRoute, 966 data: sendData, 967 } ).then( function( response ) { 968 969 self.commentSubmiting = false; 970 971 self.$root.userData.name = self.commentAuthorName; 972 self.$root.userData.mail = self.commentAuthorMail; 973 JetReviews.setLocalStorageData( 'guestName', self.commentAuthorName ); 974 JetReviews.setLocalStorageData( 'guestMail', self.commentAuthorMail ); 975 976 if ( response.success ) { 977 self.commentFormVisible = false; 978 self.commentText = ''; 979 self.itemData.comments.unshift( response.data ); 980 self.commentsVisible = true; 981 } else { 982 self.responseMessage = response.message; 983 console.log( response.message ); 984 } 985 } ).catch( function( error ) { 986 self.commentSubmiting = false; 987 self.responseMessage = error && error.message ? error.message : self.$root.userData.canComment.message; 988 } ); 989 }, 990 991 updateApprovalHandler: function( type ) { 992 let self = this, 993 altType = 'like' === type ? 'dislike' : 'like'; 994 995 this.approvalSubmiting = true; 996 997 wp.apiFetch( { 998 method: 'post', 999 path: window.jetReviewPublicConfig.likeReviewRoute, 1000 data: { 1001 review_id: self.itemData.id, 1002 type: type, 1003 inc: ! self.itemData.approval[ type ], 1004 current_state: self.itemData.approval, 1005 }, 1006 } ).then( function( response ) { 1007 self.approvalSubmiting = false; 1008 1009 if ( response.success ) { 1010 self.$set( self.itemData, 'approval', response.data.approval ); 1011 self.$set( self.itemData, 'like', response.data.like ); 1012 self.$set( self.itemData, 'dislike', response.data.dislike ); 1013 1014 if ( self.$root.isUserGuest ) { 1015 self.updateGuestApprovalData( self.itemData.id, self.itemData.approval ); 1016 } 1017 1018 } else { 1019 console.log( response.message ); 1020 } 1021 } ); 1022 }, 1023 1024 updateGuestApprovalData: function( $reviewId = false, rateData = false ) { 1025 let guestReviewRateData = JetReviews.getLocalStorageData( this.$root.guestRatedStorageName, {} ); 1026 1027 guestReviewRateData[ $reviewId ] = rateData; 1028 1029 JetReviews.setLocalStorageData( this.$root.guestRatedStorageName, guestReviewRateData ); 1030 }, 1031 1032 toggleCommentsVisible: function() { 1033 this.commentsVisible = !this.commentsVisible; 1034 }, 1035 1036 getCommentsCount: function( comments, initialValue = 0 ) { 1037 1038 let total = comments.reduce( ( accumulator, commentData ) => { 1039 if ( commentData.hasOwnProperty( 'children' ) && 0 !== commentData.children.length ) { 1040 let initialValue = accumulator + 1; 1041 return this.getCommentsCount( commentData.children, initialValue ); 1042 } else { 1043 return accumulator + 1; 1044 } 1045 }, initialValue ); 1046 1047 return total; 1048 } 1049 } 1050 }); 1051 1052 Vue.component( 'jet-advanced-reviews-comment', { 1053 template: '#jet-advanced-reviews-comment-template', 1054 1055 props: { 1056 commentData: Object, 1057 parentId: Number, 1058 parentComments: Array, 1059 depth: Number, 1060 }, 1061 1062 data: function() { 1063 return ( { 1064 commentsList: this.commentData.children || [], 1065 replySubmiting: false, 1066 replyFormVisible: false, 1067 replyText: '', 1068 replyAuthorName: '', 1069 replyAuthorMail: '', 1070 responseMessage: '' 1071 } ); 1072 }, 1073 1074 mounted: function() { 1075 if ( this.$root.isUserGuest ) {
1076 this.replyAuthorName = JetReviews.getLocalStorageData( 'guestName', '' ); 1077 this.replyAuthorMail = JetReviews.getLocalStorageData( 'guestMail', '' ); 1078 } 1079 }, 1080 1081 computed: { 1082 commentClass: function() { 1083 return ''; 1084 }, 1085 1086 formControlsVisible: function() { 1087 return this.$root.sourceData.commentsAllowed && this.$root.userData.canComment.allowed; 1088 }, 1089 1090 submitVisible: function() { 1091 1092 if ( this.$root.isUserGuest ) { 1093 return this.isReplyTextValid && 1094 this.isValidAuthorName && 1095 this.isValidAuthorEmail; 1096 } 1097 1098 return this.isReplyTextValid; 1099 }, 1100 1101 replyIcon: function() { 1102 return this.$root.refsHtml.replyButtonIcon || false; 1103 }, 1104 1105 isValidAuthorName: function() { 1106 return '' !== this.replyAuthorName; 1107 }, 1108 1109 isValidAuthorEmail: function() { 1110 return JetReviews.checkValidEmail( this.replyAuthorMail ); 1111 }, 1112 1113 isReplyTextValid: function() { 1114 return '' !== this.replyText; 1115 }, 1116 1117 authorVerificationData: function() { 1118 1119 if ( ! this.commentData.verifications ) { 1120 return false; 1121 } 1122 1123 return 0 !== this.commentData.verifications.length ? this.commentData.verifications : false; 1124 }, 1125 }, 1126 1127 methods: { 1128 showReplyForm: function() { 1129 let self = this; 1130 1131 this.replyFormVisible = !this.replyFormVisible; 1132 1133 if ( this.replyFormVisible ) { 1134 this.replyText = `${ JetReviews.normalizePlainTextToHTML( this.commentData.author.name ) }, `; 1135 1136 Vue.nextTick().then( function () { 1137 let commentText = self.$refs.commentText, 1138 textarea = commentText.$refs.textarea; 1139 1140 JetReviews.placeCaretAtEnd( textarea ); 1141 } ); 1142 } 1143 }, 1144 1145 cancelNewReply: function() { 1146 this.replyFormVisible = false; 1147 this.responseMessage = ''; 1148 }, 1149 1150 submitNewReply: function() { 1151 1152 let self = this, 1153 forSendingData = { 1154 source: this.$root.sourceData.source, 1155 source_type: this.$root.sourceData.sourceType, 1156 source_id: this.$root.sourceData.sourceId, 1157 parent_id: 0 === this.depth ? this.commentData.id : this.parentId, 1158 review_id: this.commentData.review_id, 1159 author_id: this.$root.userData.id, 1160 author_name: this.replyAuthorName, 1161 author_mail: this.replyAuthorMail, 1162 content: this.replyText, 1163 }; 1164 1165 // Recaptcha enable check 1166 if ( JetReviews.executeRecaptcha( 1167 'submit_comment_reply', 1168 function( token ) { 1169 let modifyData = Object.assign( {}, forSendingData, { 1170 captcha_token: token 1171 } ); 1172 1173 self.submitReplyHandler( modifyData ); 1174 }, 1175 function() { 1176 self.responseMessage = JetReviews.getCaptchaValidationMessage( self.$root.options.labels.captchaValidationFailed ); 1177 } 1178 ) ) { 1179 return false; 1180 } 1181 1182 this.submitReplyHandler( forSendingData ); 1183 }, 1184 1185 submitReplyHandler: function( sendData = false ) { 1186 let self = this; 1187 1188 if ( ! sendData ) { 1189 console.warn( 'Empty review comment data for sending' ); 1190 1191 return false; 1192 } 1193 1194 this.replySubmiting = true; 1195 1196 wp.apiFetch( { 1197 method: 'post', 1198 path: window.jetReviewPublicConfig.submitReviewCommentRoute, 1199 data: sendData, 1200 } ).then( function( response ) { 1201 1202 self.replySubmiting = false; 1203 1204 self.$root.userData.name = self.replyAuthorName; 1205 self.$root.userData.mail = self.replyAuthorMail; 1206 JetReviews.setLocalStorageData( 'guestName', self.replyAuthorName ); 1207 JetReviews.setLocalStorageData( 'guestMail', self.replyAuthorMail ); 1208 1209 if ( response.success ) { 1210 self.replyFormVisible = false; 1211 self.replyText = ''; 1212 1213 if ( 0 === self.depth ) { 1214 self.commentData.children.unshift( response.data ); 1215 } else { 1216 self.parentComments.push( response.data ); 1217 } 1218 } else { 1219 self.responseMessage = response.message; 1220 } 1221 } ).catch( function( error ) { 1222 self.replySubmiting = false; 1223 self.responseMessage = error && error.message ? error.message : self.$root.userData.canComment.message; 1224 } ); 1225 } 1226 } 1227 }); 1228 1229 Vue.component( 'points-field', { 1230 template: '#jet-advanced-reviews-point-field-template', 1231 1232 props: { 1233 before: { 1234 type: [ Number, String, Boolean ], 1235 default: false 1236 }, 1237 rating: Number, 1238 after: { 1239 type: [ Number, String, Boolean ], 1240 default: false 1241 }, 1242 }, 1243 1244 data: function() { 1245 return ( {} ); 1246 }, 1247 1248 computed: { 1249 isBeforeEmpty: function() { 1250 return false === this.before || '' === this.before; 1251 }, 1252 1253 isAfterEmpty: function() { 1254 return false === this.after || '' === this.after; 1255 }, 1256 1257 preparedRating: function() { 1258 1259 if ( 10 > this.rating ) { 1260 return 10; 1261 } 1262 1263 return this.rating; 1264 }, 1265 1266 ratingClass: function() { 1267 let ratingClass = 'very-high-rating'; 1268 1269 if ( this.rating >= 80 && this.rating <= 100 ) { 1270 ratingClass = 'very-high-rating'; 1271 } 1272 1273 if ( this.rating >= 60 && this.rating <= 79 ) { 1274 ratingClass = 'high-rating'; 1275 } 1276 1277 if ( this.rating >= 40 && this.rating <= 59 ) { 1278 ratingClass = 'medium-rating'; 1279 } 1280 1281 if ( this.rating >= 22 && this.rating <= 39 ) { 1282 ratingClass = 'low-rating'; 1283 } 1284 1285 if ( this.rating >= 0 && this.rating <= 21 ) { 1286 ratingClass = 'very-low-rating'; 1287 } 1288 1289 return ratingClass; 1290 } 1291 } 1292 }); 1293 1294 Vue.component( 'stars-field', { 1295 template: '#jet-advanced-reviews-star-field-template', 1296 1297 props: { 1298 before: { 1299 type: [ Number, String, Boolean ], 1300 default: false 1301 }, 1302 rating: Number, 1303 after: { 1304 type: [ Number, String, Boolean ], 1305 default: false 1306 }, 1307 }, 1308 1309 data: function() { 1310 return ( {} ); 1311 }, 1312 1313 computed: { 1314 isBeforeEmpty: function() { 1315 return ! this.before || '' === this.before; 1316 }, 1317 1318 isAfterEmpty: function() { 1319 return ! this.after || '' === this.after; 1320 }, 1321 1322 preparedRating: function() { 1323 1324 if ( 10 > this.rating ) { 1325 return 10; 1326 } 1327 1328 return this.rating; 1329 }, 1330 1331 emptyIcons: function() { 1332 let icon = `<div class="jet-reviews-star">${ this.$root.refsHtml.emptyStarIcon }</div>` || '<div class="jet-reviews-star"><i class="far fa-star"></i></div>'; 1333 1334 return icon.repeat( 5 ); 1335 }, 1336 1337 filledIcons: function() { 1338 let icon = `<div class="jet-reviews-star">${ this.$root.refsHtml.filledStarIcon }</div>` || '<div class="jet-reviews-star"><i class="fas fa-star"></i></div>'; 1339 1340 return icon.repeat( 5 ); 1341 }, 1342 1343 ratingClass: function() { 1344 let ratingClass = 'very-high-rating'; 1345 1346 if ( this.rating >= 80 && this.rating <= 100 ) { 1347 ratingClass = 'very-high-rating'; 1348 } 1349 1350 if ( this.rating >= 60 && this.rating <= 79 ) { 1351 ratingClass = 'high-rating'; 1352 } 1353 1354 if ( this.rating >= 40 && this.rating <= 59 ) { 1355 ratingClass = 'medium-rating'; 1356 } 1357 1358 if ( this.rating >= 22 && this.rating <= 39 ) { 1359 ratingClass = 'low-rating'; 1360 } 1361 1362 if ( this.rating >= 0 && this.rating <= 21 ) { 1363 ratingClass = 'very-low-rating'; 1364 } 1365 1366 return ratingClass; 1367 } 1368 }, 1369 }); 1370 1371 Vue.component( 'html-textarea', { 1372 template:'<div :class="classes" ref="textarea" contenteditable="true" tabindex="0" :data-placeholder="placeholder" :data-not-valid-label="notValidLabel" @input="updateHTML" @focus="focusHandler" @blur="blurHandler"></div>', 1373 1374 props: { 1375 value: String, 1376 isValid: { 1377 type: Boolean, 1378 default: true, 1379 }, 1380 placeholder: { 1381 type: String, 1382 default: 'Input', 1383 }, 1384 notValidLabel: { 1385 type: String, 1386 default: 'This field is required or not valid', 1387 }, 1388 }, 1389 1390 data: function() { 1391 return ( { 1392 isFocus: false, 1393 isEmpty: true, 1394 } ); 1395 }, 1396 1397 computed: { 1398 classes: function() {
1399 let classes = [ 1400 'jet-reviews-content-editable', 1401 !this.isValid ? 'jet-reviews-content-editable--not-valid' : false, 1402 this.isFocus ? 'jet-reviews-content-editable--focus' : false, 1403 this.isPlaceholder ? 'jet-reviews-content-editable--placeholder' : false, 1404 ]; 1405 1406 return classes; 1407 }, 1408 1409 isPlaceholder: function() { 1410 1411 if ( this.isFocus || ! this.isEmpty ) { 1412 return false; 1413 } 1414 1415 return true; 1416 } 1417 }, 1418 1419 mounted: function () { 1420 this.$el.innerHTML = this.value; 1421 }, 1422 1423 methods: { 1424 updateHTML: function( e ) { 1425 const html = JetReviews.normalizeEditableHTML( e.target.innerHTML ); 1426 1427 if ( '' === html ) { 1428 this.isEmpty = true; 1429 this.$emit( 'input', '' ); 1430 } else { 1431 this.isEmpty = false; 1432 this.$emit( 'input', html ); 1433 } 1434 }, 1435 1436 focusHandler: function( e ) { 1437 this.$emit( 'focus', e.target ); 1438 this.isFocus = true; 1439 }, 1440 1441 blurHandler: function( e ) { 1442 this.$emit( 'blur', e.target ); 1443 this.isFocus = false; 1444 } 1445 } 1446 }); 1447 1448 Vue.component( 'file-input', { 1449 template: '#jet-reviews-widget-file-input-template', 1450 1451 props: { 1452 allowedTypes: { 1453 type: [ Array ], 1454 default() { 1455 return [ 'image/jpeg', 'image/png', 'image/gif' ]; 1456 } 1457 }, 1458 maxFileSize: { 1459 type: [ Number ], 1460 default: 5 1461 }, 1462 uploadIcon: { 1463 type: [ String ], 1464 default: '' 1465 }, 1466 uploadControlLabel: { 1467 type: [ String ], 1468 default: 'Upload your file here' 1469 }, 1470 buttonLabel: { 1471 type: [ String ], 1472 default: 'Choose File' 1473 }, 1474 maxFileSizeLabel: { 1475 type: [ String ], 1476 default: 'Maximum size' 1477 }, 1478 }, 1479 1480 data: function() { 1481 return ( { 1482 baseClass: 'jet-reviews-widget-file-input', 1483 reviewMedia: [], 1484 reviewMediaPreview: [], 1485 messageText: '', 1486 isDragOver: false, 1487 } ); 1488 }, 1489 1490 computed: { 1491 classesList() { 1492 let classesList = [ 1493 this.baseClass, 1494 this.isDragOver ? 'is-dragover' : '', 1495 ]; 1496 1497 return classesList; 1498 }, 1499 1500 maxSizeLabel() { 1501 return `${ this.maxFileSizeLabel }: ${ this.maxFileSize }MB`; 1502 }, 1503 1504 isMessageVisible: function() { 1505 return '' !== this.messageText; 1506 }, 1507 1508 maxSize: function() { 1509 return this.maxFileSize * 1024 * 1024; 1510 }, 1511 }, 1512 1513 methods: { 1514 triggerFileInput() { 1515 this.$refs.fileInput.click(); 1516 }, 1517 1518 handleFiles( event ) { 1519 const files = Array.from( event.target.files ); 1520 this.reviewMedia = []; 1521 this.reviewMediaPreview = []; 1522 this.messageText = ''; 1523 this.$emit( 'on-change', event ); 1524 this.$emit( 'on-send-status', true ); 1525 1526 files.forEach( file => { 1527 1528 if ( ! this.allowedTypes.includes( file.type ) ) { 1529 this.$emit( 'on-send-status', false ); 1530 this.messageText = 'Not allowed file type'; 1531 1532 return; 1533 } 1534 1535 if ( file.size > this.maxSize ) { 1536 this.$emit( 'on-send-status', false ); 1537 this.messageText = 'Maximum file size exceeded'; 1538 1539 return; 1540 } 1541 1542 this.reviewMedia.push( file ); 1543 1544 const reader = new FileReader(); 1545 reader.onload = event => this.reviewMediaPreview.push( event.target.result ); 1546 reader.readAsDataURL( file ); 1547 } ); 1548 1549 this.$emit( 'on-file-change', this.reviewMedia ); 1550 }, 1551 onDragOver() { 1552 this.isDragOver = true; 1553 }, 1554 onDragLeave() { 1555 this.isDragOver = false; 1556 }, 1557 onFileDrop( event ) { 1558 this.isDragOver = false; 1559 1560 const filesList = event?.dataTransfer?.files; 1561 if ( ! filesList || filesList.length === 0 ) return; 1562 1563 const files = Array.from( filesList ); 1564 this.reviewMedia = []; 1565 this.reviewMediaPreview = []; 1566 this.messageText = ''; 1567 this.$emit( 'on-send-status', true ); 1568 1569 files.forEach( file => { 1570 1571 if ( ! this.allowedTypes.includes( file.type ) ) { 1572 this.$emit( 'on-send-status', false ); 1573 this.messageText = 'Not allowed file type'; 1574 1575 return; 1576 }; 1577 1578 if ( file.size > this.maxSize ) { 1579 this.$emit( 'on-send-status', false ); 1580 this.messageText = 'Maximum file size exceeded'; 1581 1582 return; 1583 }; 1584 1585 this.reviewMedia.push( file ); 1586 1587 const reader = new FileReader(); 1588 reader.onload = event => this.reviewMediaPreview.push( event.target.result );
1589 reader.readAsDataURL( file ); 1590 } ); 1591 1592 this.$emit( 'on-file-change', this.reviewMedia ); 1593 }, 1594 }, 1595 } ); 1596 }, 1597 1598 createJetReviewAdvancedInstance: function( instanceId, options ) { 1599 1600 if ( JetReviews.initedInstance.includes( instanceId ) ) { 1601 return; 1602 } 1603 1604 options.labels = JetReviews.decodeLabels( options.labels || {} ); 1605 1606 JetReviews.initedInstance.push( instanceId ); 1607 1608 let JetReviewAdvancedInstance = new Vue( { 1609 el: '#' + instanceId, 1610 1611 data: { 1612 uniqId: instanceId, 1613 options: options, 1614 reviewsLoaded: false, 1615 getReviewsProcessing: false, 1616 reviewsList: [], 1617 reviewsPage: 1, 1618 reviewsTotal: 0, 1619 reviewsAverageRating: 0, 1620 userData: options.userData, 1621 sourceData: options.sourceData, 1622 reviewTypeFields: options.reviewsFields, 1623 formVisible: false, 1624 isMounted: false, 1625 refsHtml: {}, 1626 }, 1627 1628 mounted: function() { 1629 let self = this, 1630 refsHtml = {}; 1631 1632 this.isMounted = true; 1633 1634 for ( var ref in this.$refs ) { 1635 Object.assign( refsHtml, { [ ref ]: this.$refs[ ref ].innerHTML } ); 1636 } 1637 1638 this.refsHtml = refsHtml; 1639 1640 if ( this.isUserGuest ) { 1641 this.$set( this.userData, 'id', this.guestId ); 1642 this.$set( this.userData, 'name', this.guestName ); 1643 this.$set( this.userData, 'mail', this.guestMail ); 1644 1645 let guestReviewedItems = JetReviews.getLocalStorageData( this.guestReviewedStorageName, [] ); 1646 1647 if ( guestReviewedItems.includes( this.sourceData.sourceId ) ) { 1648 this.$set( this.userData.canReview, 'allowed', false ); 1649 this.$set( this.userData.canReview, 'message', this.options.labels.alreadyReviewedMessage ); 1650 } 1651 } 1652 1653 if ( this.options.reviewsListData ) { 1654 this.reviewsList = this.normalizeReviewListRatingLabels( this.options.reviewsListData.list ); 1655 this.reviewsTotal = +this.options.reviewsListData.total; 1656 this.reviewsAverageRating = +this.options.reviewsListData.rating; 1657 this.reviewsLoaded = true; 1658 } else { 1659 wp.apiFetch( { 1660 method: 'post', 1661 path: window.jetReviewPublicConfig.getPublicReviewsRoute, 1662 data: { 1663 source: self.sourceData.source, 1664 source_id: self.sourceData.sourceId, 1665 page: self.reviewsPage - 1, 1666 page_size: self.options.pageSize 1667 }, 1668 } ).then( function( response ) { 1669 1670 self.reviewsLoaded = true; 1671 1672 if ( response.success && response.data ) { 1673 self.reviewsList = self.normalizeReviewListRatingLabels( response.data.list ); 1674 self.reviewsTotal = +response.data.total; 1675 self.reviewsAverageRating = +response.data.rating; 1676 } else { 1677 console.log( 'Error' ); 1678 } 1679 } ); 1680 } 1681 1682 JetReviews.eventBus.$on( 'addReview', function ( payLoad ) { 1683 1684 if ( self.options.uniqId !== payLoad.uniqId ) { 1685 return; 1686 } 1687 1688 self.formVisible = false; 1689 1690 let reviewData = payLoad.reviewData; 1691 1692 self.normalizeReviewRatingLabel( reviewData ); 1693 1694 self.reviewsList.unshift( reviewData ); 1695 1696 self.reviewsTotal += 1; 1697 } ); 1698 1699 JetReviews.eventBus.$on( 'closeNewReviewForm', function ( payLoad ) { 1700 1701 if ( self.options.uniqId !== payLoad.uniqId ) { 1702 return; 1703 } 1704 1705 self.formVisible = false; 1706 } ); 1707 1708 }, 1709 1710 computed: { 1711 instanceClass: function() { 1712 let classes = [ 1713 'jet-reviews-advanced__instance', 1714 ]; 1715 1716 if ( this.isMounted ) { 1717 classes.push( 'is-mounted' ); 1718 } 1719 1720 return classes; 1721 }, 1722 1723 reviewsLength: function() { 1724 return this.reviewsList.length; 1725 }, 1726 1727 reviewsListEmpty: function() { 1728 return 0 === this.reviewsList.length ? true : false; 1729 }, 1730 1731 preparedFields: function() { 1732 1733 let rawFields = this.reviewTypeFields, 1734 preparedFields = []; 1735 1736 for ( let fieldData of rawFields ) { 1737 preparedFields.push( { 1738 field_label: fieldData.label, 1739 field_value: +fieldData.max, 1740 field_step: +fieldData.step, 1741 field_max: +fieldData.max, 1742 } ); 1743 } 1744 1745 return preparedFields; 1746 }, 1747 1748 averageRating: function() { 1749 let totalRating = 0; 1750 1751 if ( this.reviewsListEmpty ) { 1752 return 0; 1753 } 1754 1755 totalRating = this.reviewsList.reduce( function( sum, reviewItem ) { 1756 return +reviewItem.rating + sum; 1757 }, 0 ); 1758 1759 return Math.round( totalRating / this.reviewsList.length, 1 ); 1760 }, 1761 1762 averageValue: function() { 1763 let summaryValue = 0; 1764 1765 if ( this.reviewsListEmpty ) { 1766 return 0; 1767 } 1768 1769 for ( let reviewItem of this.reviewsList ) { 1770 let ratingData = reviewItem.rating_data, 1771 itemSummary = 0; 1772 1773 for ( let ratingItem of ratingData ) { 1774 itemSummary += +ratingItem.field_value 1775 } 1776 1777 summaryValue += Math.round( itemSummary / ratingData.length, 1 ) 1778 } 1779 1780 return Math.round( summaryValue / this.reviewsList.length, 1 ); 1781 }, 1782 1783 averageMax: function() { 1784 let totalMax = 0, 1785 fields = this.reviewTypeFields; 1786 1787 totalMax = fields.reduce( function( sum, field ) { 1788 return +field.max + sum; 1789 }, 0 ); 1790 1791 return Math.round( totalMax / fields.length, 1 ); 1792 }, 1793 1794 addReviewIcon: function() { 1795 return this.refsHtml.newReviewButtonIcon || false; 1796 }, 1797 1798 isUserGuest: function() { 1799 return this.userData.roles.includes( 'guest' ); 1800 }, 1801 1802 guestId: function() {
1803 let guestId = JetReviews.getLocalStorageData( 'guestId', false ); 1804 1805 if ( ! guestId ) { 1806 let guestId = `guest_${ JetReviews.getUniqId() }`; 1807 1808 JetReviews.setLocalStorageData( 'guestId', guestId ); 1809 1810 return guestId; 1811 } 1812 1813 return guestId; 1814 }, 1815 1816 guestName: function() { 1817 let guestName = JetReviews.getLocalStorageData( 'guestName', false ); 1818 1819 if ( ! guestName ) { 1820 JetReviews.setLocalStorageData( 'guestName', '' ); 1821 1822 return ''; 1823 } 1824 1825 return guestName; 1826 }, 1827 1828 guestMail: function() { 1829 let guestMail = JetReviews.getLocalStorageData( 'guestMail', false ); 1830 1831 if ( ! guestMail ) { 1832 JetReviews.setLocalStorageData( 'guestMail', '' ); 1833 1834 return ''; 1835 } 1836 1837 return guestMail; 1838 }, 1839 1840 guestReviewedStorageName: function() { 1841 return `reviewed${ JetReviews.capitalizeString( this.sourceData.source ) }Items`; 1842 }, 1843 1844 guestRatedStorageName: function() { 1845 return `rated${ JetReviews.capitalizeString( this.sourceData.source ) }Items`; 1846 }, 1847 1848 guestNameFieldVisible: function() { 1849 return this.isUserGuest && '' === this.userData.name; 1850 }, 1851 1852 guestMailFieldVisible: function() { 1853 return this.isUserGuest && '' === this.userData.mail; 1854 }, 1855 1856 paginationVisible: function() { 1857 return this.reviewsTotal > this.options.pageSize; 1858 }, 1859 1860 }, 1861 1862 methods: { 1863 formVisibleToggle: function() { 1864 this.formVisible = !this.formVisible; 1865 }, 1866 1867 getLabelBySlug: function( slug = false ) { 1868 1869 if ( ! slug ) { 1870 return false; 1871 } 1872 1873 if ( ! window.jetReviewPublicConfig.labels.hasOwnProperty( slug ) ) { 1874 return false; 1875 } 1876 1877 return window.jetReviewPublicConfig.labels[ slug ]; 1878 }, 1879 1880 changePageHandle: function( page ) { 1881 this.reviewsPage = page; 1882 this.getReviews(); 1883 }, 1884 1885 normalizeReviewListRatingLabels: function( reviewsList = [] ) { 1886 1887 if ( ! Array.isArray( reviewsList ) ) { 1888 return []; 1889 } 1890 1891 return reviewsList.map( ( reviewItem ) => { 1892 return this.normalizeReviewRatingLabel( reviewItem ); 1893 } ); 1894 }, 1895 1896 normalizeReviewRatingLabel: function( reviewItem = {} ) { 1897 let ratingLabel = this.options.labels.ratingLabel || '', 1898 ratingLabelOverrideAllowed = this.options.hasOwnProperty( 'ratingLabelOverrideAllowed' ) ? 1899 this.options.ratingLabelOverrideAllowed : 1900 this.options.isDefaultRatingFields; 1901 1902 if ( 1903 ! ratingLabel || 1904 ! ratingLabelOverrideAllowed || 1905 ! reviewItem.rating_data || 1906 ! Array.isArray( reviewItem.rating_data ) || 1907 1 !== reviewItem.rating_data.length 1908 ) { 1909 return reviewItem; 1910 } 1911 1912 reviewItem.rating_data = reviewItem.rating_data.map( ( ratingItem ) => { 1913 ratingItem.field_label = ratingLabel; 1914 1915 return ratingItem; 1916 } ); 1917 1918 return reviewItem; 1919 }, 1920 1921 getReviews: function() { 1922 let self = this; 1923 1924 self.getReviewsProcessing = true; 1925 1926 wp.apiFetch( { 1927 method: 'post', 1928 path: window.jetReviewPublicConfig.getPublicReviewsRoute, 1929 data: { 1930 source: self.sourceData.source, 1931 source_id: self.sourceData.sourceId, 1932 page: self.reviewsPage - 1, 1933 page_size: self.options.pageSize, 1934 }, 1935 } ).then( function( response ) { 1936 self.getReviewsProcessing = false; 1937 1938 self.scrollToInstance(); 1939 1940 if ( response.success && response.data ) { 1941 self.reviewsList = self.normalizeReviewListRatingLabels( response.data.list ); 1942 self.reviewsTotal = +response.data.total; 1943 self.reviewsAverageRating = +response.data.rating; 1944 } else { 1945 console.log( 'Error' ); 1946 } 1947 } ); 1948 }, 1949 1950 scrollToInstance: function() { 1951 let $instance = $( `#${ this.uniqId }` ), 1952 offsetTop = $instance.offset().top - 50; 1953 1954 window.scrollTo( { 1955 top: offsetTop, 1956 behavior: 'smooth', 1957 } ); 1958 } 1959 } 1960 1961 } ); 1962 }, 1963 1964 getLocalStorageData: function( _key = false, _default = false ) { 1965 try { 1966 let jetReviewsData = JSON.parse( window.localStorage.getItem( 'jetReviewsData' ) ); 1967 1968 if ( _key ) { 1969 1970 if ( jetReviewsData.hasOwnProperty( _key ) ) { 1971 return jetReviewsData[ _key ]; 1972 } else { 1973 return _default; 1974 } 1975 } 1976 1977 return jetReviewsData; 1978 } catch ( e ) { 1979 return _default; 1980 } 1981 }, 1982 1983 setLocalStorageData: function( key, data ) { 1984 let jetReviewsData = this.getLocalStorageData() || {}; 1985 1986 jetReviewsData[ key ] = data; 1987 1988 window.localStorage.setItem( 'jetReviewsData', JSON.stringify( jetReviewsData ) ); 1989 }, 1990 1991 getUniqId: function() { 1992 return Math.random().toString( 36 ).substr( 2, 9 ); 1993 }, 1994 1995 capitalizeString: function( string = '' ) { 1996 1997 if ( typeof string !== 'string' ) { 1998 return '' 1999 } 2000 2001 return string.charAt( 0 ).toUpperCase() + string.slice( 1 ); 2002 }, 2003 2004 escapeHTML: function( string = '' ) { 2005 2006 if ( typeof string !== 'string' ) { 2007 return ''; 2008 } 2009 2010 return string 2011 .replace( /&/g, '&' ) 2012 .replace( /</g, '<' ) 2013 .replace( />/g, '>' ) 2014 .replace( /"/g, '"' ) 2015 .replace( /'/g, ''' ); 2016 }, 2017 2018 decodeHTML: function( string = '' ) { 2019 2020 if ( typeof string !== 'string' ) { 2021 return ''; 2022 } 2023 2024 let textarea = document.createElement( 'textarea' ); 2025 2026 textarea.innerHTML = string; 2027 2028 return textarea.value; 2029 }, 2030 2031 decodeLabels: function( labels = {} ) { 2032 let decodedLabels = {}; 2033 2034 if ( ! labels || typeof labels !== 'object' ) { 2035 return decodedLabels; 2036 } 2037 2038 for ( let slug in labels ) { 2039 2040 if ( ! labels.hasOwnProperty( slug ) ) { 2041 continue; 2042 } 2043 2044 decodedLabels[ slug ] = JetReviews.decodeHTML( labels[ slug ] ); 2045 } 2046 2047 return decodedLabels; 2048 }, 2049 2050 normalizePlainTextToHTML: function( string = '' ) { 2051 2052 if ( typeof string !== 'string' ) { 2053 return ''; 2054 } 2055 2056 return JetReviews.escapeHTML( string ) 2057 .replace( / {2,}/g, function( match ) { 2058 return ' ' + ' '.repeat( match.length - 1 ); 2059 } ) 2060 .replace( /\n/g, '<br>' ); 2061 }, 2062 2063 normalizeEditableHTML: function( string = '' ) { 2064 2065 if ( typeof string !== 'string' ) { 2066 return ''; 2067 } 2068 2069 let prepared = string 2070 .replace( /\r\n?/g, '\n' ) 2071 .replace( /<div><br><\/div>/gi, '\n' ) 2072 .replace( /<\/div>\s*<div>/gi, '\n' ) 2073 .replace( /<div>/gi, '' ) 2074 .replace( /<\/div>/gi, '' ) 2075 .replace( /<br\s*\/?>/gi, '\n' ); 2076 2077 const container = document.createElement( 'div' ); 2078 2079 container.innerHTML = prepared; 2080 prepared = container.textContent || container.innerText || ''; 2081 prepared = prepared 2082 .replace( /\u00A0/g, ' ' ) 2083 .replace( /\u200B/g, '' ) 2084 .trim(); 2085 2086 return JetReviews.normalizePlainTextToHTML( prepared ); 2087 }, 2088 2089 placeCaretAtEnd: function( el ) { 2090 el.focus(); 2091 2092 if ( 'undefined' !== typeof window.getSelection && 'undefined' !== typeof document.createRange ) { 2093 let range = document.createRange(); 2094 2095 range.selectNodeContents( el ); 2096 range.collapse( false ); 2097 2098 let selection = window.getSelection(); 2099 2100 selection.removeAllRanges(); 2101 selection.addRange( range ); 2102 } else if ( 'undefined' !== typeof document.body.createTextRange ) { 2103 let textRange = document.body.createTextRange(); 2104 2105 textRange.moveToElementText( el ); 2106 textRange.collapse( false ); 2107 textRange.select(); 2108 } 2109 }, 2110 2111 checkValidEmail: function ( email ) { 2112 var reg = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 2113 2114 return reg.test( email ); 2115 }, 2116 }; 2117 2118 window.JetReviews = JetReviews; 2119 2120 JetReviews.init(); 2121 2122}( jQuery, window.jetReviewPublicConfig ) ); 2123 2124// Elementor Public Scripts 2125( function( $, elementorFrontend, publicConfig ) { 2126 'use strict'; 2127 2128 let JetReviewsElementor = { 2129 init: function() { 2130 2131 var widgets = { 2132 'jet-reviews.default' : JetReviewsElementor.widgetJetReviewsSimple, 2133 'jet-reviews-advanced.default' : JetReviewsElementor.widgetJetReviewsAdvanced, 2134 }; 2135 2136 $.each( widgets, function( widget, callback ) { 2137 elementorFrontend.hooks.addAction( 'frontend/element_ready/' + widget, callback ); 2138 } ); 2139 }, 2140 2141 widgetJetReviewsSimple: function( $scope ) { 2142 var $target = $scope.find( '.jet-review' ), 2143 settings = $target.data( 'settings' ), 2144 $form = $( '.jet-review__form', $target ), 2145 $submitButton = $( '.jet-review__form-submit', $target ), 2146 $removeButton = $( '.jet-review__item-remove', $target ), 2147 $message = $( '.jet-review__form-message', $target ), 2148 $rangeControl = $( '.jet-review__form-field.type-range input', $target ), 2149 ajaxRequest = null; 2150 2151 $rangeControl.on( 'input', function( event ) { 2152 var $this = $( this ), 2153 $parent = $this.closest( '.jet-review__form-field' ), 2154 $currentValue = $( '.current-value', $parent ), 2155 value = $this.val(); 2156 2157 $currentValue.html( value ); 2158 } ); 2159 2160 $submitButton.on( 'click.widgetJetReviews', function() { 2161 addReviewHandle(); 2162 2163 return false;
2164 } ); 2165 2166 $removeButton.on( 'click.widgetJetReviews', function() { 2167 var $this = $( this ); 2168 2169 removeReviewHandle( $this ); 2170 2171 return false; 2172 } ); 2173 2174 function addReviewHandle() { 2175 var now = new Date(), 2176 reviewTime = now.getTime(), 2177 reviewDate = new Date( reviewTime ).toLocaleString(), 2178 sendData = { 2179 'post_id': settings['post_id'], 2180 'review_time': reviewTime, 2181 'review_date': reviewDate 2182 }, 2183 serializeArray = $form.serializeObject(); 2184 2185 sendData = jQuery.extend( sendData, serializeArray ); 2186 2187 ajaxRequest = jQuery.ajax( { 2188 type: 'POST', 2189 url: window.jetReviewPublicConfig.ajax_url, 2190 data: { 2191 'action': 'jet_reviews_add_meta_review', 2192 '_ajax_nonce': window.jetReviewPublicConfig.ajax_nonce, 2193 'data': sendData 2194 }, 2195 beforeSend: function( jqXHR, ajaxSettings ) { 2196 if ( null !== ajaxRequest ) { 2197 ajaxRequest.abort(); 2198 } 2199 2200 $submitButton.addClass( 'load-state' ); 2201 }, 2202 error: function( jqXHR, ajaxSettings ) { 2203 2204 }, 2205 success: function( data, textStatus, jqXHR ) { 2206 2207 var responseType = data['type'], 2208 message = data.message || ''; 2209 2210 if ( 'error' === responseType ) { 2211 $submitButton.removeClass( 'load-state' ); 2212 $message.addClass( 'visible-state' ); 2213 2214 $( 'span', $message ).html( message ); 2215 } 2216 2217 if ( 'success' === responseType ) { 2218 location.reload(); 2219 } 2220 } 2221 } ); 2222 }; 2223 2224 function removeReviewHandle( $removeButton ) { 2225 var $reviewItem = $removeButton.closest( '.jet-review__item' ), 2226 reviewUserId = $reviewItem.data( 'user-id' ), 2227 sendData = { 2228 'post_id': settings['post_id'], 2229 'user_id': reviewUserId 2230 }; 2231 2232 ajaxRequest = jQuery.ajax( { 2233 type: 'POST', 2234 url: window.jetReviewPublicConfig.ajax_url, 2235 data: { 2236 'action': 'jet_reviews_remove_review', 2237 '_ajax_nonce': window.jetReviewPublicConfig.ajax_nonce, 2238 'data': sendData 2239 }, 2240 beforeSend: function( jqXHR, ajaxSettings ) { 2241 if ( null !== ajaxRequest ) { 2242 ajaxRequest.abort(); 2243 } 2244 2245 $removeButton.addClass( 'load-state' ); 2246 }, 2247 error: function( jqXHR, ajaxSettings ) { 2248 2249 }, 2250 success: function( data, textStatus, jqXHR ) { 2251 var successType = data.type, 2252 message = data.message || ''; 2253 2254 if ( 'error' == successType ) { 2255 2256 } 2257 2258 if ( 'success' == successType ) { 2259 location.reload(); 2260 } 2261 } 2262 } ); 2263 }; 2264 }, 2265 2266 widgetJetReviewsAdvanced: function( $scope ) { 2267 let $target = $scope.find( '.jet-reviews-advanced' ), 2268 instanceId = $target.attr( 'id' ), 2269 uniqid = $target.data( 'uniqid' ) || false, 2270 options = window['jetReviewsWidget' + uniqid] || {}; 2271 2272 if ( ! $target[0] || ! uniqid ) { 2273 return; 2274 } 2275 2276 window.JetReviews.createJetReviewAdvancedInstance( instanceId, options ); 2277 }, 2278 } 2279 2280 $( window ).on( 'elementor/frontend/init', JetReviewsElementor.init ); 2281 2282 $.fn.serializeObject = function(){ 2283 2284 var self = this, 2285 json = {}, 2286 push_counters = {}, 2287 patterns = { 2288 "validate": /^[a-zA-Z][a-zA-Z0-9_-]*(?:\[(?:\d*|[a-zA-Z0-9_-]+)\])*$/, 2289 "key": /[a-zA-Z0-9_-]+|(?=\[\])/g, 2290 "push": /^$/, 2291 "fixed": /^\d+$/, 2292 "named": /^[a-zA-Z0-9_-]+$/ 2293 }; 2294 2295 this.build = function(base, key, value){ 2296 base[key] = value; 2297 return base; 2298 }; 2299 2300 this.push_counter = function(key){ 2301 if(push_counters[key] === undefined){ 2302 push_counters[key] = 0; 2303 } 2304 return push_counters[key]++; 2305 }; 2306 2307 $.each($(this).serializeArray(), function(){ 2308 // skip invalid keys 2309 if(!patterns.validate.test(this.name)){ 2310 return; 2311 } 2312 2313 var k, 2314 keys = this.name.match(patterns.key), 2315 merge = this.value, 2316 reverse_key = this.name; 2317 2318 while((k = keys.pop()) !== undefined){ 2319 2320 // adjust reverse_key 2321 reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), ''); 2322 2323 // push 2324 if(k.match(patterns.push)){ 2325 merge = self.build([], self.push_counter(reverse_key), merge); 2326 } 2327 2328 // fixed 2329 else if(k.match(patterns.fixed)){ 2330 merge = self.build([], k, merge); 2331 } 2332 2333 // named 2334 else if(k.match(patterns.named)){ 2335 merge = self.build({}, k, merge); 2336 } 2337 } 2338 2339 json = $.extend(true, json, merge); 2340 }); 2341 2342 return json; 2343 }; 2344 2345}( jQuery, window.elementorFrontend, window.jetReviewPublicConfig ) );
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.