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
		
			
				
	
	
		
			928 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			928 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* global wpforms_gutenberg_form_selector */
 | |
| /* jshint es3: false, esversion: 6 */
 | |
| 
 | |
| /**
 | |
|  * @param wpforms_gutenberg_form_selector.route_namespace
 | |
|  * @param strings.theme_name
 | |
|  * @param strings.theme_delete
 | |
|  * @param strings.theme_delete_title
 | |
|  * @param strings.theme_delete_confirm
 | |
|  * @param strings.theme_delete_cant_undone
 | |
|  * @param strings.theme_delete_yes
 | |
|  * @param strings.theme_copy
 | |
|  * @param strings.theme_custom
 | |
|  * @param strings.theme_noname
 | |
|  * @param strings.button_background
 | |
|  * @param strings.button_text
 | |
|  * @param strings.field_label
 | |
|  * @param strings.field_sublabel
 | |
|  * @param strings.field_border
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Gutenberg editor block.
 | |
|  *
 | |
|  * Themes panel module.
 | |
|  *
 | |
|  * @since 1.8.8
 | |
|  */
 | |
| export default ( function( document, window, $ ) {
 | |
| 	/**
 | |
| 	 * WP core components.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 */
 | |
| 	const { PanelBody, ColorIndicator, TextControl, Button } = wp.components;
 | |
| 	const { __experimentalRadio: Radio, __experimentalRadioGroup: RadioGroup } = wp.components;
 | |
| 
 | |
| 	/**
 | |
| 	 * Localized data aliases.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 */
 | |
| 	const { isAdmin, isPro, isLicenseActive, strings, route_namespace: routeNamespace } = wpforms_gutenberg_form_selector;
 | |
| 
 | |
| 	/**
 | |
| 	 * Form selector common module.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 *
 | |
| 	 * @type {Object}
 | |
| 	 */
 | |
| 	let formSelectorCommon = null;
 | |
| 
 | |
| 	/**
 | |
| 	 * Runtime state.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 *
 | |
| 	 * @type {Object}
 | |
| 	 */
 | |
| 	const state = {};
 | |
| 
 | |
| 	/**
 | |
| 	 * Themes data.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 *
 | |
| 	 * @type {Object}
 | |
| 	 */
 | |
| 	const themesData = {
 | |
| 		wpforms: null,
 | |
| 		custom: null,
 | |
| 	};
 | |
| 
 | |
| 	/**
 | |
| 	 * Enabled themes.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 *
 | |
| 	 * @type {Object}
 | |
| 	 */
 | |
| 	let enabledThemes = null;
 | |
| 
 | |
| 	/**
 | |
| 	 * Elements holder.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 *
 | |
| 	 * @type {Object}
 | |
| 	 */
 | |
| 	const el = {};
 | |
| 
 | |
| 	/**
 | |
| 	 * Public functions and properties.
 | |
| 	 *
 | |
| 	 * @since 1.8.8
 | |
| 	 *
 | |
| 	 * @type {Object}
 | |
| 	 */
 | |
| 	const app = {
 | |
| 		/**
 | |
| 		 * Initialize panel.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 */
 | |
| 		init() {
 | |
| 			el.$window = $( window );
 | |
| 
 | |
| 			app.fetchThemesData();
 | |
| 
 | |
| 			$( app.ready );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Document ready.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 */
 | |
| 		ready() {
 | |
| 			app.events();
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Events.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 */
 | |
| 		events() {
 | |
| 			wp.data.subscribe( function() { // eslint-disable-line complexity
 | |
| 				if ( ! isAdmin ) {
 | |
| 					return;
 | |
| 				}
 | |
| 
 | |
| 				const isSavingPost = wp.data.select( 'core/editor' )?.isSavingPost();
 | |
| 				const isAutosavingPost = wp.data.select( 'core/editor' )?.isAutosavingPost();
 | |
| 				const isSavingWidget = wp.data.select( 'core/edit-widgets' )?.isSavingWidgetAreas();
 | |
| 				const currentPost = wp.data.select( 'core/editor' )?.getCurrentPost();
 | |
| 				const isBlockOrTemplate = currentPost?.type?.includes( 'wp_template' ) || currentPost?.type?.includes( 'wp_block' );
 | |
| 
 | |
| 				if ( ( ! isSavingPost && ! isSavingWidget && ! isBlockOrTemplate ) || isAutosavingPost ) {
 | |
| 					return;
 | |
| 				}
 | |
| 
 | |
| 				if ( isBlockOrTemplate ) {
 | |
| 					// Delay saving if this is FSE for better performance.
 | |
| 					_.debounce( app.saveCustomThemes, 500 )();
 | |
| 
 | |
| 					return;
 | |
| 				}
 | |
| 
 | |
| 				app.saveCustomThemes();
 | |
| 			} );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get all themes data.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @return {Object} Themes data.
 | |
| 		 */
 | |
| 		getAllThemes() {
 | |
| 			return { ...( themesData.custom || {} ), ...( themesData.wpforms || {} ) };
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get theme data.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} slug Theme slug.
 | |
| 		 *
 | |
| 		 * @return {Object|null} Theme settings.
 | |
| 		 */
 | |
| 		getTheme( slug ) {
 | |
| 			return app.getAllThemes()[ slug ] || null;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get enabled themes data.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @return {Object} Themes data.
 | |
| 		 */
 | |
| 		getEnabledThemes() {
 | |
| 			if ( enabledThemes ) {
 | |
| 				return enabledThemes;
 | |
| 			}
 | |
| 
 | |
| 			const allThemes = app.getAllThemes();
 | |
| 
 | |
| 			if ( isPro && isLicenseActive ) {
 | |
| 				return allThemes;
 | |
| 			}
 | |
| 
 | |
| 			enabledThemes = Object.keys( allThemes ).reduce( ( acc, key ) => {
 | |
| 				if ( allThemes[ key ].settings?.fieldSize && ! allThemes[ key ].disabled ) {
 | |
| 					acc[ key ] = allThemes[ key ];
 | |
| 				}
 | |
| 				return acc;
 | |
| 			}, {} );
 | |
| 
 | |
| 			return enabledThemes;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Update enabled themes.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} slug  Theme slug.
 | |
| 		 * @param {Object} theme Theme settings.
 | |
| 		 */
 | |
| 		updateEnabledThemes( slug, theme ) {
 | |
| 			if ( ! enabledThemes ) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			enabledThemes = {
 | |
| 				...enabledThemes,
 | |
| 				[ slug ]: theme,
 | |
| 			};
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Whether the theme is disabled.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} slug Theme slug.
 | |
| 		 *
 | |
| 		 * @return {boolean} True if the theme is disabled.
 | |
| 		 */
 | |
| 		isDisabledTheme( slug ) {
 | |
| 			return ! app.getEnabledThemes()?.[ slug ];
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Whether the theme is one of the WPForms themes.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} slug Theme slug.
 | |
| 		 *
 | |
| 		 * @return {boolean} True if the theme is one of the WPForms themes.
 | |
| 		 */
 | |
| 		isWPFormsTheme( slug ) {
 | |
| 			return Boolean( themesData.wpforms[ slug ]?.settings );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Fetch themes data from API.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 */
 | |
| 		fetchThemesData() {
 | |
| 			// If a fetch is already in progress, exit the function.
 | |
| 			if ( state.isFetchingThemes || themesData.wpforms ) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			// Set the flag to true indicating a fetch is in progress.
 | |
| 			state.isFetchingThemes = true;
 | |
| 
 | |
| 			try {
 | |
| 				// Fetch themes data.
 | |
| 				wp.apiFetch( {
 | |
| 					path: routeNamespace + 'themes/',
 | |
| 					method: 'GET',
 | |
| 					cache: 'no-cache',
 | |
| 				} )
 | |
| 					.then( ( response ) => {
 | |
| 						themesData.wpforms = response.wpforms || {};
 | |
| 						themesData.custom = response.custom || {};
 | |
| 					} )
 | |
| 					.catch( ( error ) => {
 | |
| 						// eslint-disable-next-line no-console
 | |
| 						console.error( error?.message );
 | |
| 					} )
 | |
| 					.finally( () => {
 | |
| 						state.isFetchingThemes = false;
 | |
| 					} );
 | |
| 			} catch ( error ) {
 | |
| 				// eslint-disable-next-line no-console
 | |
| 				console.error( error );
 | |
| 			}
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Save custom themes.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 */
 | |
| 		saveCustomThemes() {
 | |
| 			// Custom themes do not exist.
 | |
| 			if ( state.isSavingThemes || ! themesData.custom ) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			// Set the flag to true indicating a saving is in progress.
 | |
| 			state.isSavingThemes = true;
 | |
| 
 | |
| 			try {
 | |
| 				// Save themes.
 | |
| 				wp.apiFetch( {
 | |
| 					path: routeNamespace + 'themes/custom/',
 | |
| 					method: 'POST',
 | |
| 					data: { customThemes: themesData.custom },
 | |
| 				} )
 | |
| 					.then( ( response ) => {
 | |
| 						if ( ! response?.result ) {
 | |
| 							// eslint-disable-next-line no-console
 | |
| 							console.log( response?.error );
 | |
| 						}
 | |
| 					} )
 | |
| 					.catch( ( error ) => {
 | |
| 						// eslint-disable-next-line no-console
 | |
| 						console.error( error?.message );
 | |
| 					} )
 | |
| 					.finally( () => {
 | |
| 						state.isSavingThemes = false;
 | |
| 					} );
 | |
| 			} catch ( error ) {
 | |
| 				// eslint-disable-next-line no-console
 | |
| 				console.error( error );
 | |
| 			}
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get the current style attributes state.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props Block properties.
 | |
| 		 *
 | |
| 		 * @return {boolean} Whether the custom theme is created.
 | |
| 		 */
 | |
| 		getCurrentStyleAttributes( props ) {
 | |
| 			const defaultAttributes = Object.keys( themesData.wpforms.default?.settings );
 | |
| 			const currentStyleAttributes = {};
 | |
| 
 | |
| 			for ( const key in defaultAttributes ) {
 | |
| 				const attr = defaultAttributes[ key ];
 | |
| 
 | |
| 				currentStyleAttributes[ attr ] = props.attributes[ attr ] ?? '';
 | |
| 			}
 | |
| 
 | |
| 			return currentStyleAttributes;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Maybe create custom theme.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props Block properties.
 | |
| 		 *
 | |
| 		 * @return {boolean} Whether the custom theme is created.
 | |
| 		 */
 | |
| 		maybeCreateCustomTheme( props ) { // eslint-disable-line complexity
 | |
| 			const currentStyles = app.getCurrentStyleAttributes( props );
 | |
| 			const isWPFormsTheme = !! themesData.wpforms[ props.attributes.theme ];
 | |
| 			const isCustomTheme = !! themesData.custom[ props.attributes.theme ];
 | |
| 
 | |
| 			let migrateToCustomTheme = false;
 | |
| 
 | |
| 			// It is one of the default themes without any changes.
 | |
| 			if (
 | |
| 				isWPFormsTheme &&
 | |
| 				JSON.stringify( themesData.wpforms[ props.attributes.theme ]?.settings ) === JSON.stringify( currentStyles )
 | |
| 			) {
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			const prevAttributes = formSelectorCommon.getBlockRuntimeStateVar( props.clientId, 'prevAttributesState' );
 | |
| 
 | |
| 			// It is a block added in FS 1.0, so it doesn't have a theme.
 | |
| 			// The `prevAttributes` is `undefined` means that we are in the first render of the existing block.
 | |
| 			if ( props.attributes.theme === 'default' && props.attributes.themeName === '' && ! prevAttributes ) {
 | |
| 				migrateToCustomTheme = true;
 | |
| 			}
 | |
| 
 | |
| 			// It is a modified default theme OR unknown custom theme.
 | |
| 			if ( isWPFormsTheme || ! isCustomTheme || migrateToCustomTheme ) {
 | |
| 				app.createCustomTheme( props, currentStyles, migrateToCustomTheme );
 | |
| 			}
 | |
| 
 | |
| 			return true;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Create custom theme.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object}  props                Block properties.
 | |
| 		 * @param {Object}  currentStyles        Current style settings.
 | |
| 		 * @param {boolean} migrateToCustomTheme Whether it is needed to migrate to custom theme.
 | |
| 		 *
 | |
| 		 * @return {boolean} Whether the custom theme is created.
 | |
| 		 */
 | |
| 		createCustomTheme( props, currentStyles = null, migrateToCustomTheme = false ) { // eslint-disable-line complexity
 | |
| 			let counter = 0;
 | |
| 			let themeSlug = props.attributes.theme;
 | |
| 
 | |
| 			const baseTheme = app.getTheme( props.attributes.theme ) || themesData.wpforms.default;
 | |
| 			let themeName = baseTheme.name;
 | |
| 
 | |
| 			themesData.custom = themesData.custom || {};
 | |
| 
 | |
| 			if ( migrateToCustomTheme ) {
 | |
| 				themeSlug = 'custom';
 | |
| 				themeName = strings.theme_custom;
 | |
| 			}
 | |
| 
 | |
| 			// Determine the theme slug and the number of copies.
 | |
| 			do {
 | |
| 				counter++;
 | |
| 				themeSlug = themeSlug + '-copy-' + counter;
 | |
| 			} while ( themesData.custom[ themeSlug ] && counter < 10000 );
 | |
| 
 | |
| 			const copyStr = counter < 2 ? strings.theme_copy : strings.theme_copy + ' ' + counter;
 | |
| 
 | |
| 			themeName += ' (' + copyStr + ')';
 | |
| 
 | |
| 			// The first migrated Custom Theme should be without `(Copy)` suffix.
 | |
| 			themeName = migrateToCustomTheme && counter < 2 ? strings.theme_custom : themeName;
 | |
| 
 | |
| 			// Add the new custom theme.
 | |
| 			themesData.custom[ themeSlug ] = {
 | |
| 				name: themeName,
 | |
| 				settings: currentStyles || app.getCurrentStyleAttributes( props ),
 | |
| 			};
 | |
| 
 | |
| 			app.updateEnabledThemes( themeSlug, themesData.custom[ themeSlug ] );
 | |
| 
 | |
| 			// Update the block attributes with the new custom theme settings.
 | |
| 			props.setAttributes( {
 | |
| 				theme: themeSlug,
 | |
| 				themeName,
 | |
| 			} );
 | |
| 
 | |
| 			return true;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Maybe create custom theme by given attributes.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} attributes Block properties.
 | |
| 		 *
 | |
| 		 * @return {string} New theme's slug.
 | |
| 		 */
 | |
| 		maybeCreateCustomThemeFromAttributes( attributes ) { // eslint-disable-line complexity
 | |
| 			const newThemeSlug = attributes.theme;
 | |
| 			const existingTheme = app.getTheme( attributes.theme );
 | |
| 			const keys = Object.keys( attributes );
 | |
| 
 | |
| 			let isExistingTheme = Boolean( existingTheme?.settings );
 | |
| 
 | |
| 			// Check if the theme already exists and has the same settings.
 | |
| 			if ( isExistingTheme ) {
 | |
| 				for ( const i in keys ) {
 | |
| 					const key = keys[ i ];
 | |
| 
 | |
| 					if ( ! existingTheme.settings[ key ] || existingTheme.settings[ key ] !== attributes[ key ] ) {
 | |
| 						isExistingTheme = false;
 | |
| 
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// The theme exists and has the same settings.
 | |
| 			if ( isExistingTheme ) {
 | |
| 				return newThemeSlug;
 | |
| 			}
 | |
| 
 | |
| 			// The theme doesn't exist.
 | |
| 			// Normalize the attributes to the default theme settings.
 | |
| 			const defaultAttributes = Object.keys( themesData.wpforms.default.settings );
 | |
| 			const newSettings = {};
 | |
| 
 | |
| 			for ( const i in defaultAttributes ) {
 | |
| 				const attr = defaultAttributes[ i ];
 | |
| 
 | |
| 				newSettings[ attr ] = attributes[ attr ] ?? '';
 | |
| 			}
 | |
| 
 | |
| 			// Create a new custom theme.
 | |
| 			themesData.custom[ newThemeSlug ] = {
 | |
| 				name: attributes.themeName ?? strings.theme_custom,
 | |
| 				settings: newSettings,
 | |
| 			};
 | |
| 
 | |
| 			app.updateEnabledThemes( newThemeSlug, themesData.custom[ newThemeSlug ] );
 | |
| 
 | |
| 			return newThemeSlug;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Update custom theme.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} attribute Attribute name.
 | |
| 		 * @param {string} value     New attribute value.
 | |
| 		 * @param {Object} props     Block properties.
 | |
| 		 */
 | |
| 		updateCustomThemeAttribute( attribute, value, props ) { // eslint-disable-line complexity
 | |
| 			const themeSlug = props.attributes.theme;
 | |
| 
 | |
| 			// Skip if it is one of the WPForms themes OR the attribute is not in the theme settings.
 | |
| 			if (
 | |
| 				themesData.wpforms[ themeSlug ] ||
 | |
| 				(
 | |
| 					attribute !== 'themeName' &&
 | |
| 					! themesData.wpforms.default.settings[ attribute ]
 | |
| 				)
 | |
| 			) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			// Skip if the custom theme doesn't exist.
 | |
| 			// It should never happen, only in some unique circumstances.
 | |
| 			if ( ! themesData.custom[ themeSlug ] ) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			// Update theme data.
 | |
| 			if ( attribute === 'themeName' ) {
 | |
| 				themesData.custom[ themeSlug ].name = value;
 | |
| 			} else {
 | |
| 				themesData.custom[ themeSlug ].settings = themesData.custom[ themeSlug ].settings || themesData.wpforms.default.settings;
 | |
| 				themesData.custom[ themeSlug ].settings[ attribute ] = value;
 | |
| 			}
 | |
| 
 | |
| 			// Trigger event for developers.
 | |
| 			el.$window.trigger( 'wpformsFormSelectorUpdateTheme', [ themeSlug, themesData.custom[ themeSlug ], props ] );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get Themes panel JSX code.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props                    Block properties.
 | |
| 		 * @param {Object} formSelectorCommonModule Common module.
 | |
| 		 * @param {Object} stockPhotosModule        StockPhotos module.
 | |
| 		 *
 | |
| 		 * @return {Object} Themes panel JSX code.
 | |
| 		 */
 | |
| 		getThemesPanel( props, formSelectorCommonModule, stockPhotosModule ) {
 | |
| 			// Store common module in app.
 | |
| 			formSelectorCommon = formSelectorCommonModule;
 | |
| 			state.stockPhotos = stockPhotosModule;
 | |
| 
 | |
| 			// If there are no themes data, it is necessary to fetch it firstly.
 | |
| 			if ( ! themesData.wpforms ) {
 | |
| 				app.fetchThemesData();
 | |
| 
 | |
| 				// Return empty JSX code.
 | |
| 				return ( <></> );
 | |
| 			}
 | |
| 
 | |
| 			// Get event handlers.
 | |
| 			const handlers = app.getEventHandlers( props );
 | |
| 			const showCustomThemeOptions = isAdmin && formSelectorCommonModule.isFullStylingEnabled() && app.maybeCreateCustomTheme( props );
 | |
| 			const checked = formSelectorCommonModule.isFullStylingEnabled() ? props.attributes.theme : 'classic';
 | |
| 			const isLeadFormsEnabled = formSelectorCommonModule.isLeadFormsEnabled( formSelectorCommonModule.getBlockContainer( props ) );
 | |
| 			const displayLeadFormNotice = isLeadFormsEnabled ? 'block' : 'none';
 | |
| 			const modernNoticeStyles = displayLeadFormNotice === 'block' ? { display: 'none' } : {};
 | |
| 
 | |
| 			let classes = formSelectorCommon.getPanelClass( props, 'themes' );
 | |
| 
 | |
| 			classes += isLeadFormsEnabled ? ' wpforms-lead-forms-enabled' : '';
 | |
| 			classes += app.isMac() ? ' wpforms-is-mac' : '';
 | |
| 
 | |
| 			return (
 | |
| 				<PanelBody className={ classes } title={ strings.themes }>
 | |
| 					<p className="wpforms-gutenberg-panel-notice wpforms-warning wpforms-use-modern-notice" style={ modernNoticeStyles }>
 | |
| 						<strong>{ strings.use_modern_notice_head }</strong>
 | |
| 						{ strings.use_modern_notice_text } <a href={ strings.use_modern_notice_link } rel="noreferrer" target="_blank">{ strings.learn_more }</a>
 | |
| 					</p>
 | |
| 
 | |
| 					<p className="wpforms-gutenberg-panel-notice wpforms-warning wpforms-lead-form-notice" style={ { display: displayLeadFormNotice } }>
 | |
| 						<strong>{ strings.lead_forms_panel_notice_head }</strong>
 | |
| 						{ strings.lead_forms_panel_notice_text }
 | |
| 					</p>
 | |
| 
 | |
| 					<RadioGroup
 | |
| 						className="wpforms-gutenberg-form-selector-themes-radio-group"
 | |
| 						label={ strings.themes }
 | |
| 						checked={ checked }
 | |
| 						defaultChecked={ props.attributes.theme }
 | |
| 						onChange={ ( value ) => handlers.selectTheme( value ) }
 | |
| 					>
 | |
| 						{ app.getThemesItemsJSX( props ) }
 | |
| 					</RadioGroup>
 | |
| 					{ showCustomThemeOptions && (
 | |
| 						<>
 | |
| 							<TextControl
 | |
| 								className="wpforms-gutenberg-form-selector-themes-theme-name"
 | |
| 								label={ strings.theme_name }
 | |
| 								value={ props.attributes.themeName }
 | |
| 								onChange={ ( value ) => handlers.changeThemeName( value ) }
 | |
| 							/>
 | |
| 
 | |
| 							<Button isSecondary
 | |
| 								className="wpforms-gutenberg-form-selector-themes-delete"
 | |
| 								onClick={ handlers.deleteTheme }
 | |
| 								buttonSettings=""
 | |
| 							>
 | |
| 								{ strings.theme_delete }
 | |
| 							</Button>
 | |
| 						</>
 | |
| 					) }
 | |
| 				</PanelBody>
 | |
| 			);
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get the Themes panel items JSX code.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props Block properties.
 | |
| 		 *
 | |
| 		 * @return {Array} Themes items JSX code.
 | |
| 		 */
 | |
| 		getThemesItemsJSX( props ) { // eslint-disable-line complexity
 | |
| 			const allThemesData = app.getAllThemes();
 | |
| 
 | |
| 			if ( ! allThemesData ) {
 | |
| 				return [];
 | |
| 			}
 | |
| 
 | |
| 			const itemsJsx = [];
 | |
| 			const themes = Object.keys( allThemesData );
 | |
| 			let theme, firstThemeSlug;
 | |
| 
 | |
| 			// Display the current custom theme on the top of the list.
 | |
| 			if ( ! app.isWPFormsTheme( props.attributes.theme ) ) {
 | |
| 				firstThemeSlug = props.attributes.theme;
 | |
| 
 | |
| 				itemsJsx.push(
 | |
| 					app.getThemesItemJSX(
 | |
| 						props.attributes.theme,
 | |
| 						app.getTheme( props.attributes.theme )
 | |
| 					)
 | |
| 				);
 | |
| 			}
 | |
| 
 | |
| 			for ( const key in themes ) {
 | |
| 				const slug = themes[ key ];
 | |
| 
 | |
| 				// Skip the first theme.
 | |
| 				if ( firstThemeSlug && firstThemeSlug === slug ) {
 | |
| 					continue;
 | |
| 				}
 | |
| 
 | |
| 				// Ensure that all the theme settings are present.
 | |
| 				theme = { ...allThemesData.default, ...( allThemesData[ slug ] || {} ) };
 | |
| 				theme.settings = { ...allThemesData.default.settings, ...( theme.settings || {} ) };
 | |
| 
 | |
| 				itemsJsx.push( app.getThemesItemJSX( slug, theme ) );
 | |
| 			}
 | |
| 
 | |
| 			return itemsJsx;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get the Themes panel's single item JSX code.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} slug  Theme slug.
 | |
| 		 * @param {Object} theme Theme data.
 | |
| 		 *
 | |
| 		 * @return {Object|null} Themes panel single item JSX code.
 | |
| 		 */
 | |
| 		getThemesItemJSX( slug, theme ) {
 | |
| 			if ( ! theme ) {
 | |
| 				return null;
 | |
| 			}
 | |
| 
 | |
| 			const title = theme.name?.length > 0 ? theme.name : strings.theme_noname;
 | |
| 			let radioClasses = 'wpforms-gutenberg-form-selector-themes-radio';
 | |
| 
 | |
| 			radioClasses += app.isDisabledTheme( slug ) ? ' wpforms-gutenberg-form-selector-themes-radio-disabled' : ' wpforms-gutenberg-form-selector-themes-radio-enabled';
 | |
| 
 | |
| 			return (
 | |
| 				<Radio
 | |
| 					value={ slug }
 | |
| 					title={ title }
 | |
| 				>
 | |
| 					<div
 | |
| 						className={ radioClasses }
 | |
| 					>
 | |
| 						<div className="wpforms-gutenberg-form-selector-themes-radio-title">{ title }</div>
 | |
| 					</div>
 | |
| 					<div className="wpforms-gutenberg-form-selector-themes-indicators">
 | |
| 						<ColorIndicator colorValue={ theme.settings.buttonBackgroundColor } title={ strings.button_background } data-index="0" />
 | |
| 						<ColorIndicator colorValue={ theme.settings.buttonTextColor } title={ strings.button_text } data-index="1" />
 | |
| 						<ColorIndicator colorValue={ theme.settings.labelColor } title={ strings.field_label } data-index="2" />
 | |
| 						<ColorIndicator colorValue={ theme.settings.labelSublabelColor } title={ strings.field_sublabel } data-index="3" />
 | |
| 						<ColorIndicator colorValue={ theme.settings.fieldBorderColor } title={ strings.field_border } data-index="4" />
 | |
| 					</div>
 | |
| 				</Radio>
 | |
| 			);
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Set block theme.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props     Block properties.
 | |
| 		 * @param {string} themeSlug The theme slug.
 | |
| 		 *
 | |
| 		 * @return {boolean} True on success.
 | |
| 		 */
 | |
| 		setBlockTheme( props, themeSlug ) {
 | |
| 			if ( app.maybeDisplayUpgradeModal( themeSlug ) ) {
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			const theme = app.getTheme( themeSlug );
 | |
| 
 | |
| 			if ( ! theme?.settings ) {
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			const attributes = Object.keys( theme.settings );
 | |
| 			const block = formSelectorCommon.getBlockContainer( props );
 | |
| 			const container = block.querySelector( `#wpforms-${ props.attributes.formId }` );
 | |
| 
 | |
| 			// Overwrite block attributes with the new theme settings.
 | |
| 			// It is needed to rely on the theme settings only.
 | |
| 			const newProps = { ...props, attributes: { ...props.attributes, ...theme.settings } };
 | |
| 
 | |
| 			// Update the preview with the new theme settings.
 | |
| 			for ( const key in attributes ) {
 | |
| 				const attr = attributes[ key ];
 | |
| 
 | |
| 				theme.settings[ attr ] = theme.settings[ attr ] === '0' ? '0px' : theme.settings[ attr ];
 | |
| 
 | |
| 				formSelectorCommon.updatePreviewCSSVarValue(
 | |
| 					attr,
 | |
| 					theme.settings[ attr ],
 | |
| 					container,
 | |
| 					newProps
 | |
| 				);
 | |
| 			}
 | |
| 
 | |
| 			// Prepare the new attributes to be set.
 | |
| 			const setAttributes = {
 | |
| 				theme: themeSlug,
 | |
| 				themeName: theme.name,
 | |
| 				...theme.settings,
 | |
| 			};
 | |
| 
 | |
| 			if ( props.setAttributes ) {
 | |
| 				// Update the block attributes with the new theme settings.
 | |
| 				props.setAttributes( setAttributes );
 | |
| 			}
 | |
| 
 | |
| 			// Trigger event for developers.
 | |
| 			el.$window.trigger( 'wpformsFormSelectorSetTheme', [ block, themeSlug, props ] );
 | |
| 
 | |
| 			return true;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Maybe display upgrades modal in Lite.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {string} themeSlug The theme slug.
 | |
| 		 *
 | |
| 		 * @return {boolean} True if modal was displayed.
 | |
| 		 */
 | |
| 		maybeDisplayUpgradeModal( themeSlug ) {
 | |
| 			if ( ! app.isDisabledTheme( themeSlug ) ) {
 | |
| 				return false;
 | |
| 			}
 | |
| 
 | |
| 			if ( ! isPro ) {
 | |
| 				formSelectorCommon.education.showProModal( 'themes', strings.themes );
 | |
| 
 | |
| 				return true;
 | |
| 			}
 | |
| 
 | |
| 			if ( ! isLicenseActive ) {
 | |
| 				formSelectorCommon.education.showLicenseModal( 'themes', strings.themes, 'select-theme' );
 | |
| 
 | |
| 				return true;
 | |
| 			}
 | |
| 
 | |
| 			return false;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Get themes panel event handlers.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props Block properties.
 | |
| 		 *
 | |
| 		 * @type {Object}
 | |
| 		 */
 | |
| 		getEventHandlers( props ) { // eslint-disable-line max-lines-per-function
 | |
| 			const commonHandlers = formSelectorCommon.getSettingsFieldsHandlers( props );
 | |
| 
 | |
| 			const handlers = {
 | |
| 				/**
 | |
| 				 * Select theme event handler.
 | |
| 				 *
 | |
| 				 * @since 1.8.8
 | |
| 				 *
 | |
| 				 * @param {string} value New attribute value.
 | |
| 				 */
 | |
| 				selectTheme( value ) {
 | |
| 					if ( ! app.setBlockTheme( props, value ) ) {
 | |
| 						return;
 | |
| 					}
 | |
| 
 | |
| 					// Maybe open Stock Photo installation window.
 | |
| 					state?.stockPhotos?.onSelectTheme( value, props, app, commonHandlers );
 | |
| 
 | |
| 					const block = formSelectorCommon.getBlockContainer( props );
 | |
| 
 | |
| 					formSelectorCommon.setTriggerServerRender( false );
 | |
| 					commonHandlers.updateCopyPasteContent();
 | |
| 
 | |
| 					// Trigger event for developers.
 | |
| 					el.$window.trigger( 'wpformsFormSelectorSelectTheme', [ block, props, value ] );
 | |
| 				},
 | |
| 
 | |
| 				/**
 | |
| 				 * Change theme name event handler.
 | |
| 				 *
 | |
| 				 * @since 1.8.8
 | |
| 				 *
 | |
| 				 * @param {string} value New attribute value.
 | |
| 				 */
 | |
| 				changeThemeName( value ) {
 | |
| 					formSelectorCommon.setTriggerServerRender( false );
 | |
| 					props.setAttributes( { themeName: value } );
 | |
| 
 | |
| 					app.updateCustomThemeAttribute( 'themeName', value, props );
 | |
| 				},
 | |
| 
 | |
| 				/**
 | |
| 				 * Delete theme event handler.
 | |
| 				 *
 | |
| 				 * @since 1.8.8
 | |
| 				 */
 | |
| 				deleteTheme() {
 | |
| 					const deleteThemeSlug = props.attributes.theme;
 | |
| 
 | |
| 					// Remove theme from the theme storage.
 | |
| 					delete themesData.custom[ deleteThemeSlug ];
 | |
| 
 | |
| 					// Open the confirmation modal window.
 | |
| 					app.deleteThemeModal( props, deleteThemeSlug, handlers );
 | |
| 				},
 | |
| 			};
 | |
| 
 | |
| 			return handlers;
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Open the theme delete confirmation modal window.
 | |
| 		 *
 | |
| 		 * @since 1.8.8
 | |
| 		 *
 | |
| 		 * @param {Object} props           Block properties.
 | |
| 		 * @param {string} deleteThemeSlug Theme slug.
 | |
| 		 * @param {Object} handlers        Block event handlers.
 | |
| 		 */
 | |
| 		deleteThemeModal( props, deleteThemeSlug, handlers ) {
 | |
| 			const confirm = strings.theme_delete_confirm.replace( '%1$s', `<b>${ props.attributes.themeName }</b>` );
 | |
| 			const content = `<p class="wpforms-theme-delete-text">${ confirm } ${ strings.theme_delete_cant_undone }</p>`;
 | |
| 
 | |
| 			$.confirm( {
 | |
| 				title: strings.theme_delete_title,
 | |
| 				content,
 | |
| 				icon: 'wpforms-exclamation-circle',
 | |
| 				type: 'red',
 | |
| 				buttons: {
 | |
| 					confirm: {
 | |
| 						text: strings.theme_delete_yes,
 | |
| 						btnClass: 'btn-confirm',
 | |
| 						keys: [ 'enter' ],
 | |
| 						action() {
 | |
| 							// Switch to the default theme.
 | |
| 							handlers.selectTheme( 'default' );
 | |
| 
 | |
| 							// Trigger event for developers.
 | |
| 							el.$window.trigger( 'wpformsFormSelectorDeleteTheme', [ deleteThemeSlug, props ] );
 | |
| 						},
 | |
| 					},
 | |
| 					cancel: {
 | |
| 						text: strings.cancel,
 | |
| 						keys: [ 'esc' ],
 | |
| 					},
 | |
| 				},
 | |
| 			} );
 | |
| 		},
 | |
| 
 | |
| 		/**
 | |
| 		 * Determine if the user is on a Mac.
 | |
| 		 *
 | |
| 		 * @return {boolean} True if the user is on a Mac.
 | |
| 		 */
 | |
| 		isMac() {
 | |
| 			return navigator.userAgent.includes( 'Macintosh' );
 | |
| 		},
 | |
| 	};
 | |
| 
 | |
| 	app.init();
 | |
| 
 | |
| 	// Provide access to public functions/properties.
 | |
| 	return app;
 | |
| }( document, window, jQuery ) );
 |