Add massive collection of CSS, JavaScript and theme assets that were previously excluded: **CSS Files (681 total):** - HVAC plugin-specific styles (hvac-*.css): 34 files including dashboard, certificates, registration, mobile nav, accessibility fixes, animations, and welcome popup - Theme framework files (Astra, builder systems, layouts): 200+ files - Plugin compatibility styles (WooCommerce, WPForms, Elementor, Contact Form 7): 150+ files - WordPress core and editor styles: 50+ files - Responsive and RTL language support: 200+ files **JavaScript Files (400+ total):** - HVAC plugin functionality (hvac-*.js): 27 files including menu systems, dashboard enhancements, profile sharing, mobile responsive features, accessibility, and animations - Framework and library files: jQuery plugins, GSAP, AOS, Swiper, Chart.js, Lottie, Isotope - Plugin compatibility scripts: WPForms, WooCommerce, Elementor, Contact Form 7, LifterLMS - WordPress core functionality: customizer, admin, block editor compatibility - Third-party integrations: Stripe, SMTP, analytics, search functionality **Assets:** - Certificate background images and logos - Comprehensive theme styling infrastructure - Mobile-responsive design systems - Cross-browser compatibility assets - Performance-optimized minified versions **Updated .gitignore:** - Fixed asset directory whitelisting patterns to properly include CSS/JS/images - Added proper directory structure recognition (!/assets/css/, !/assets/js/, etc.) - Maintains security by excluding sensitive files while including essential assets This commit provides the complete frontend infrastructure needed for: - Full theme functionality and styling - Plugin feature implementations - Mobile responsiveness and accessibility - Cross-browser compatibility - Performance optimization - Developer workflow support
		
			
				
	
	
		
			314 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* global WPFormsAIChatHTMLElement, WPFormsBuilder, wpf, wpforms_builder */
 | |
| 
 | |
| /**
 | |
|  * The WPForms AI chat element.
 | |
|  *
 | |
|  * Choices helpers module.
 | |
|  *
 | |
|  * @since 1.9.1
 | |
|  *
 | |
|  * @param {WPFormsAIChatHTMLElement} chat The chat element.
 | |
|  *
 | |
|  * @return {Object} The choices' helpers object.
 | |
|  */
 | |
| export default function( chat ) { // eslint-disable-line max-lines-per-function
 | |
| 	/**
 | |
| 	 * The `choices` mode helpers object.
 | |
| 	 *
 | |
| 	 * @since 1.9.1
 | |
| 	 */
 | |
| 	return {
 | |
| 		/**
 | |
| 		 * Get the `choices` answer based on AI response data.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @param {Object} response The response data.
 | |
| 		 *
 | |
| 		 * @return {string} Answer HTML markup.
 | |
| 		 */
 | |
| 		getAnswer( response ) {
 | |
| 			if ( response.choices?.length < 1 ) {
 | |
| 				return '';
 | |
| 			}
 | |
| 
 | |
| 			const li = [];
 | |
| 
 | |
| 			for ( const i in response.choices ) {
 | |
| 				li.push( `
 | |
| 					<li class="wpforms-ai-chat-choices-item">
 | |
| 						${ chat.htmlSpecialChars( response.choices[ i ] ) }
 | |
| 					</li>
 | |
| 				` );
 | |
| 			}
 | |
| 
 | |
| 			let answerHtml = `
 | |
| 				<h4>${ chat.htmlSpecialChars( response.heading ?? '' ) }</h4>
 | |
| 				<ol>
 | |
| 					${ li.join( '' ) }
 | |
| 				</ol>
 | |
| 			`;
 | |
| 
 | |
| 			// Add footer to the first answer only.
 | |
| 			if ( ! chat.sessionId ) {
 | |
| 				answerHtml += `<span>${ chat.modeStrings.footer }</span>`;
 | |
| 			}
 | |
| 
 | |
| 			return answerHtml;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get the answer pre-buttons HTML markup.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @return {string} The answer pre-buttons HTML markup.
 | |
| 		 */
 | |
| 		getAnswerButtonsPre() {
 | |
| 			return `
 | |
| 				<button type="button" class="wpforms-ai-chat-choices-insert wpforms-ai-chat-answer-action wpforms-btn-sm wpforms-btn-orange" >
 | |
| 					<span>${ chat.modeStrings.insert }</span>
 | |
| 				</button>
 | |
| 			`;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get the warning message HTML markup.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @return {string} The warning message HTML markup.
 | |
| 		 */
 | |
| 		getWarningMessage() {
 | |
| 			// Trigger event before warning message insert.
 | |
| 			chat.triggerEvent( 'wpformsAIModalBeforeWarningMessageInsert', { fieldId: chat.fieldId } );
 | |
| 
 | |
| 			return `<div class="wpforms-ai-chat-divider"></div>
 | |
| 					<div class="wpforms-chat-item-notice">
 | |
| 						<div class="wpforms-chat-item-notice-content">
 | |
| 							<span>${ chat.modeStrings.warning }</span>
 | |
| 						</div>
 | |
| 					</div>`;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * If the field has default choices, the welcome screen is active.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @return {boolean} True if the field has default choices, false otherwise.
 | |
| 		 */
 | |
| 		isWelcomeScreen() {
 | |
| 			const items = document.getElementById( `wpforms-field-option-row-${ chat.fieldId }-choices` )
 | |
| 				.querySelectorAll( 'li input.label' );
 | |
| 
 | |
| 			if ( items.length === 1 && ! items[ 0 ].value.trim() ) {
 | |
| 				return true;
 | |
| 			}
 | |
| 
 | |
| 			if ( items.length > 3 ) {
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			const defaults = Object.values( chat.modeStrings.defaults );
 | |
| 
 | |
| 			for ( let i = 0; i < items.length; i++ ) {
 | |
| 				if ( ! defaults.includes( items[ i ].value ) ) {
 | |
| 					return false;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return true;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Add the `choices` answer.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @param {HTMLElement} element The answer element.
 | |
| 		 */
 | |
| 		addedAnswer( element ) {
 | |
| 			const button = element.querySelector( '.wpforms-ai-chat-choices-insert' );
 | |
| 
 | |
| 			// Listen to the button click event.
 | |
| 			button?.addEventListener( 'click', this.insertButtonClick.bind( this ) );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Sanitize response.
 | |
| 		 *
 | |
| 		 * @since 1.9.2
 | |
| 		 *
 | |
| 		 * @param {Object} response The response data to sanitize.
 | |
| 		 *
 | |
| 		 * @return {Object} The sanitized response.
 | |
| 		 */
 | |
| 		sanitizeResponse( response ) {
 | |
| 			if ( ! Array.isArray( response?.choices ) ) {
 | |
| 				return response;
 | |
| 			}
 | |
| 
 | |
| 			let choices = response.choices;
 | |
| 
 | |
| 			// Sanitize choices.
 | |
| 			choices = choices.map( ( choice ) => {
 | |
| 				return wpf.sanitizeHTML( choice, wpforms_builder.allowed_label_html_tags );
 | |
| 			} );
 | |
| 
 | |
| 			// Remove empty choices.
 | |
| 			response.choices = choices.filter( ( choice ) => {
 | |
| 				return choice.trim() !== '';
 | |
| 			} );
 | |
| 
 | |
| 			return response;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Check if the response has a prohibited code.
 | |
| 		 *
 | |
| 		 * @since 1.9.2
 | |
| 		 *
 | |
| 		 * @param {Object} response          The response data.
 | |
| 		 * @param {Array}  sanitizedResponse The sanitized response data.
 | |
| 		 *
 | |
| 		 * @return {boolean} Whether the answer has a prohibited code.
 | |
| 		 */
 | |
| 		hasProhibitedCode( response, sanitizedResponse ) {
 | |
| 			// If the number of choices has changed after sanitization, it means that the answer contains prohibited code.
 | |
| 			return sanitizedResponse?.choices?.length !== response?.choices?.length;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Click on the Use Choices button.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @param {Event} e The event object.
 | |
| 		 */
 | |
| 		insertButtonClick( e ) {
 | |
| 			const button = e.target;
 | |
| 			const answer = button.closest( '.wpforms-chat-item.wpforms-chat-item-choices' );
 | |
| 			const responseId = answer?.getAttribute( 'data-response-id' );
 | |
| 			const choicesList = answer?.querySelector( 'ol' );
 | |
| 			const items = choicesList.querySelectorAll( '.wpforms-ai-chat-choices-item' );
 | |
| 			const choiceItems = [];
 | |
| 
 | |
| 			// Get choices data.
 | |
| 			for ( const i in items ) {
 | |
| 				if ( ! items.hasOwnProperty( i ) || ! items[ i ].textContent ) {
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				choiceItems.push( items[ i ].textContent.trim() );
 | |
| 			}
 | |
| 
 | |
| 			// Rate the response.
 | |
| 			chat.wpformsAiApi.rate( true, responseId );
 | |
| 
 | |
| 			// Replace field choices.
 | |
| 			this.replaceChoices( choiceItems );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Replace field choices.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @param {Array} choices Choices array.
 | |
| 		 */
 | |
| 		replaceChoices( choices ) {
 | |
| 			const choicesOptionRow = document.getElementById( `wpforms-field-option-row-${ chat.fieldId }-choices` );
 | |
| 			const choicesList = choicesOptionRow.querySelector( 'ul.choices-list' );
 | |
| 			const choiceRow = choicesList.querySelector( 'li:first-child' ).cloneNode( true );
 | |
| 
 | |
| 			choiceRow.innerHTML = choiceRow.innerHTML.replace( /\[choices\]\[\d+\]/g, `[choices][{{key}}]` );
 | |
| 
 | |
| 			// Clear existing choices.
 | |
| 			choicesList.innerHTML = '';
 | |
| 
 | |
| 			// Add new choices.
 | |
| 			for ( const i in choices ) {
 | |
| 				const key = ( Number( i ) + 1 ).toString();
 | |
| 				const choice = choices[ i ];
 | |
| 
 | |
| 				// Clone choice item element.
 | |
| 				let li = choiceRow.cloneNode( true );
 | |
| 
 | |
| 				// Get updated single choice item.
 | |
| 				li = this.getUpdatedSingleChoiceItem( li, key, choice );
 | |
| 
 | |
| 				// Add new choice item.
 | |
| 				choicesList.appendChild( li );
 | |
| 			}
 | |
| 
 | |
| 			// Update data-next-id attribute for choices list.
 | |
| 			choicesList.setAttribute( 'data-next-id', choices.length + 1 );
 | |
| 
 | |
| 			// Update field preview.
 | |
| 			const fieldOptions = document.getElementById( `wpforms-field-option-${ chat.fieldId }` );
 | |
| 			const fieldType = fieldOptions.querySelector( 'input.wpforms-field-option-hidden-type' )?.value;
 | |
| 
 | |
| 			WPFormsBuilder.fieldChoiceUpdate( fieldType, chat.fieldId, choices.length );
 | |
| 			WPFormsBuilder.triggerBuilderEvent( 'wpformsFieldChoiceAdd' );
 | |
| 
 | |
| 			// Trigger event after choices insert.
 | |
| 			chat.triggerEvent( 'wpformsAIModalAfterChoicesInsert', { fieldId: chat.fieldId } );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get updated single choice item.
 | |
| 		 *
 | |
| 		 * @since 1.9.1
 | |
| 		 *
 | |
| 		 * @param {HTMLElement} li     Choice item element.
 | |
| 		 * @param {string}      key    Choice key.
 | |
| 		 * @param {string}      choice Choice value.
 | |
| 		 *
 | |
| 		 * @return {HTMLElement} The updated choice item.
 | |
| 		 */
 | |
| 		getUpdatedSingleChoiceItem( li, key, choice ) {
 | |
| 			li.setAttribute( 'data-key', key.toString() );
 | |
| 
 | |
| 			// Update choice item inputs name attributes.
 | |
| 			li.innerHTML = li.innerHTML.replaceAll( '{{key}}', key );
 | |
| 
 | |
| 			// Sanitize choice before set.
 | |
| 			choice = wpf.sanitizeHTML( choice );
 | |
| 
 | |
| 			const inputDefault = li.querySelector( 'input.default' );
 | |
| 
 | |
| 			inputDefault.removeAttribute( 'checked' );
 | |
| 
 | |
| 			// Set label
 | |
| 			const inputLabel = li.querySelector( 'input.label' );
 | |
| 
 | |
| 			inputLabel.value = choice;
 | |
| 			inputLabel.setAttribute( 'value', choice );
 | |
| 
 | |
| 			// Set value.
 | |
| 			const inputValue = li.querySelector( 'input.value' );
 | |
| 
 | |
| 			inputValue.value = choice;
 | |
| 			inputValue.setAttribute( 'value', choice );
 | |
| 
 | |
| 			// Reset image upload.
 | |
| 			const imageUpload = li.querySelector( '.wpforms-image-upload' );
 | |
| 			const inputImage = imageUpload.querySelector( 'input.source' );
 | |
| 
 | |
| 			inputImage.value = '';
 | |
| 			inputImage.setAttribute( 'value', '' );
 | |
| 			imageUpload.querySelector( '.preview' ).innerHTML = '';
 | |
| 			imageUpload.querySelector( '.wpforms-image-upload-add' ).style.display = 'block';
 | |
| 
 | |
| 			// Reset icon choice.
 | |
| 			const iconSelect = li.querySelector( '.wpforms-icon-select' );
 | |
| 
 | |
| 			iconSelect.querySelector( '.ic-fa-preview' ).setAttribute( 'class', 'ic-fa-preview ic-fa-regular ic-fa-face-smile' );
 | |
| 			iconSelect.querySelector( 'input.source-icon' ).value = 'face-smile';
 | |
| 			iconSelect.querySelector( 'input.source-icon-style' ).value = 'regular';
 | |
| 
 | |
| 			return li;
 | |
| 		},
 | |
| 	};
 | |
| }
 |