PageSourceSearch

https://nationalcioreview.com/wp-content/plugins/jet-reviews/assets/js/jet-reviews-frontend.js?ver=3.1.2.1

js nationalcioreview.com collected 2026-09-24 09:41:24 UTC 59,466 bytes, 2,345 lines download raw bytes

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, '&amp;' )
2012				.replace( /</g, '&lt;' )
2013				.replace( />/g, '&gt;' )
2014				.replace( /"/g, '&quot;' )
2015				.replace( /'/g, '&#039;' );
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 ' ' + '&nbsp;'.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.