/** * Available spaces: * * _window.$usb - Basic object for mounting and initializing all extensions of the builder * _window.$usbcore - Auxiliary functions for the builder and his extensions * _window.usGlobalData - Data for import into the USBuilder * _window.$usof - UpSolution CSS Framework * _window.$ush - US Helper Library * * Note: Double underscore `__funcname` is introduced for functions that are created through `$ush.debounce(...)` */ ! function( $, _undefined ) { const _window = window; _window.$ush = _window.$ush || {}; _window.usGlobalData = _window.usGlobalData || {}; // Types of notifications // @see builder/assets/js/common/notify.js _window.NOTIFY_TYPE = { ERROR: 'error', INFO: 'info', SUCCESS: 'success' }; // The type of data history being used // @see builder/assets/js/builder/history.js _window.HISTORY_TYPE = { REDO: 'redo', UNDO: 'undo' }; // Actions that are applied when content changes // @see builder/assets/js/builder/history.js _window.ACTION_CONTENT = { CALLBACK: 'callback', // recovery via callback function CREATE: 'create', // create new shortcode and add to content MOVE: 'move', // move shortcode REMOVE: 'remove', // remove shortcode from content UPDATE: 'update' // update shortcode in content }; // Private temp data var _$tmp = { xhr: {} // XMLHttpRequests }; /** * @type {{}} Base object of the configurations of the entire developer */ var _$config = { actions: { // list of available builder actions site_settings: 'us-site-settings', // default }, }; /** * @class USBInit - Basic object for mounting and initializing all extensions of the builder * @param {String} container The main container */ function USBInit( container ) { const self = this; // Private "Variables" self.iframe; self.iframeIsReady = false; self._hotkeyStates = {}; // all hotkey states // This event is needed to get various data from the iframe _window.onmessage = self._onMessage.bind( self ); // Bondable events self._events = { iframeReady: $ush.debounce( self._iframeReady.bind( self ), 0 ), urlManager: self._urlManager.bind( self ), keydown: self._keydown.bind( self ), }; $( () => { // Base elements self.$document = $( document ); self.$body = $( 'body' ); // Elements self.$container = $( container ); self.$panel = $( '#usb-panel' ); self.$panelBody = $( '.usb-panel-body', self.$panel ); self.$iframe = $( 'iframe', self.$container ); self.iframe = self.$iframe[0]; // Note: The object stores all received config from // the backend, this is a single entry point for config if ( self.$container.is( '[onclick]' ) ) { $usb.setConfig( $.extend( _$config, self.$container[0].onclick() || {} ) ); self.$container.removeAttr( 'onclick' ); } // Initialize frame reads if ( self.$iframe.is( '[data-src]' ) ) { self.$iframe .attr( 'src', self.$iframe.data( 'src' ) ) .removeAttr( 'data-src' ); } // Events self.$document.on( 'keydown', self._events.keydown ); // capture keyboard shortcuts // The add information from `UserAgent` to bind styles to specific browsers or browser versions $( 'html' ).attr( 'data-useragent', $ush.ua ); // Set MacOS shortcuts if ( $ush.isMacOS ) { $( '[data-macos-shortcuts]', self.$container ).each( ( _, node ) => { const $node = $( node ); $node.text( $node.data( 'macos-shortcuts' ) || '' ); } ); } // Run URL manager after ready self._urlManager( self.urlManager.getDataOfChange() ); } ); // Private events self .on( 'iframe.pageDataLoaded', self._events.iframeReady ) .on( 'urlManager.changed', self._events.urlManager ); } /** * @type {Prototype} */ const prototype = USBInit.prototype; // Private events $.extend( prototype, $ush.mixinEvents, { /** * Handler of change or move event on the history stack * * @event handler * @param {{}|undefined} state Data object associated with history and current loaction */ _urlManager: function( state ) { const self = this; var action = state.setParams.action; if ( $usbcore.indexOf( action, self.config( 'actions', [] ) ) === -1 ) { action = self.config( 'actions.site_settings' ); // default as it is always available } // Add action class to the body of the page to apply specific styles when available self.$body.usMod( 'action', action || /* remove */false ); }, /** * Redirect messages from a frame to an object event * * @event handler * @param {Event} e * @param void */ _onMessage: function( e ) { const self = this; var data; try { data = JSON.parse( e.data ); } catch ( err ) { return; } if ( data instanceof Array && data[0] === 'usb' && data[1] !== _undefined ) { self.trigger( data[1], data[2] || [] ); } }, /** * Iframe ready event handler * * @event handler */ _iframeReady: function() { const self = this; self.iframeIsReady = true; if ( ! self.iframe.contentDocument ) { return; } self.preview.hidePreloader(); self.trigger( 'iframeReady' ); }, /** * Keyboard shortcut capture handler * Note: When the developer panel is open, it keydown may not work due to focus outside the document * * @event handler * @param {Event} e */ _keydown: function( e ) { const self = this; if ( e.type !== 'keydown' ) { return; } var isCmd = ( e.metaKey || e.ctrlKey ), // Define hotkey states (see https://ss64.com/ascii.html) isUndo = ( isCmd && ! e.shiftKey && e.which === 90 ), // `(command|ctrl)+z` combination isRedo = ( isCmd && e.shiftKey && e.which === 90 ), // `(command|ctrl)+shift+z` combination isSave = ( isCmd && ! e.shiftKey && e.which === 83 ); // `(command|ctrl)+s` combination if ( $.isPlainObject( self._hotkeyStates ) ) { // Add states to the self._hotkeyStates $.extend( self._hotkeyStates, { 'ctrl+z': isUndo, 'ctrl+shift+z': isRedo, 'ctrl+s': isSave } ); // Trigger an event if the combination is successful for ( const combination in self._hotkeyStates ) { if ( self._hotkeyStates[ combination ] === true ) { self.trigger( `hotkeys.${combination}` ); // .on( 'hotkeys.ctrl+shift+z', ... ) } } } if ( isSave // exclude page save events when using 'ctrl+s' combination || ( // exclude events the context of which form elements ( isUndo || isRedo ) && $.inArray( $ush.toLowerCase( e.target.tagName || '' ), [ 'input', 'textarea' ] ) > -1 ) ) { e.preventDefault(); } }, } ); // Methods for send data and events outside the builder $.extend( prototype, { /** * Send message to iframe * * @param {String} eventType A string contain event type * @param {[]} extraParams Additional parameters to pass along to the event handler * @chainable */ postMessage: function( eventType, extraParams ) { const self = this; if ( ! self.iframeIsReady ) { return; } self.iframe.contentWindow.postMessage( JSON.stringify( [ /* namespace */'usb', eventType, extraParams ] ) ); }, /** * Forward events through document * Note: Used to pass events through the document to other scripts, e.g. for $usof objects * * @param {String} eventType Type of event * @param {[]} extraParams Additional params to pass along to the event handler * @chainable */ triggerDocument: function( eventType, extraParams ) { this.$document.trigger( /* namespace */'usb.' + eventType, extraParams ); } }); // USBInit API $.extend( prototype, { /** * Find a property by path with respect to the root ($usb) * Note: If there is no property or object, it does not cause an error, but returns 'undefined'! * * @param {String} path Dot-delimited path to get value from object * @param {*} defaultValue Default value when no result * @return {*} Returns a value if successful, otherwise 'undefined' */ find: function( path, defaultValue ) { return path && $usbcore.deepFind( this, path, defaultValue || _undefined ); }, /** * Reload current preview page */ reloadPreview: function() { const self = this; if ( ! self.iframeIsReady ) { return; } self.preview.showPreloader(); self.iframe.contentWindow.location.reload(); }, /** * Set the configuration in the global object * * @param {{}} config The configuration */ setConfig: function( config ) { if ( $.isPlainObject( config ) ) { _$config = $.extend( true, _$config, config ); $usbcore.cache( 'config' ).flush(); } }, /** * Get config value * Note: The method is called many times, so performance is important here! * * @param {String} path Dot-delimited path to get value from config objects * @param {*} defaultValue Default value when not in configs * @return {*} Returns values from configuration */ config: function( path, defaultValue ) { // By default, we return a copy of the config without the ability to change it! if ( $ush.isUndefined( path ) ) { return $ush.clone( _$config ); } // Note: All parameters are cached as this is a static view of the data return $usbcore.cache( 'config' ).get( path, function() { // If there is no data in the cache, we will return the data to be saved in the cache return $usbcore.deepFind( _$config, path, defaultValue ); } ); }, /** * Determining if a license is activated * * @return {Boolean} True if activated, False otherwise */ licenseIsActivated: function() { return !! this.config( 'license_is_activated', /* default */false ); }, /** * Determining if a license is real activated * * @return {Boolean} True if activated, False otherwise */ licenseIsRealActivated: function() { return !! this.config( 'license_is_real_activated', /* default */false ); }, /** * Checks for hotkey combination usage by key * * @param {String} ...keys The short command keys * @return {Boolean} Returns True if used, otherwise False */ hotkeys: function() { const args = $ush.toArray( arguments ); for ( const i in args ) if ( this._hotkeyStates[ '' + args[ i ] ] === true ) { return true; } return false; }, /** * Outputs a message or objects to the web console * * @param {String} text * @param {*} data */ log: function() { var args = $ush.toArray( arguments ); if ( ! args.length ) { args = [ 'log: called with no params' ]; } console.log.apply( null, args ); }, /** * Get text translation by key * * @param {String} key The key * @return {String} The text */ getTextTranslation: function( key ) { if ( ! key ) { return ''; } return ( _window.usGlobalData.textTranslations || {} )[ key ] || key; }, /** * Normalizing instructions for previews * Note: `instructions = true` - force an ajax request to get the element code * * @param {*} instructions Instructions for preview elements * @return {*} */ _normalizeInstructions: function( instructions ) { // The convert to an array of instructions if ( !! instructions && ! Array.isArray( instructions ) && instructions !== true ) { instructions = $.isPlainObject( instructions ) ? [ instructions ] : []; } return instructions; }, /** * Convert pattern to string from result * * @param {String} template The string template * @param {{}} params The parameters { key: 'value'... } * @return {String} */ buildString: function( template, params ) { if ( ! $.isPlainObject( params ) ) { params = {}; } var self = this, // Create pattern for regular expression. Variable example: `{%var_name%}` pattern = $ush.escapePcre( $usb.config( 'startSymbol', '{%' ) ); pattern += '([A-z\\_\\d]+)'; pattern += $ush.escapePcre( $usb.config( 'endSymbol', '%}' ) ); // Replace all variables with values return ( '' + template ).replace( new RegExp( pattern, 'gm' ), ( _, varName ) => { return '' + ( params[ varName ] || '' ); } ); }, /** * Get the attachment * * @param {Number} id The attachment id * @return {{}} */ getAttachment: function( id ) { if ( ! id || ! wp.media ) { return; } return wp.media.attachment( id ); }, /** * Get the attachment url * * @param {Number} id The attachment id * @return {String} */ getAttachmentUrl: function( id ) { if ( ! id ) { return ''; } return ( this.getAttachment( id ) || { get: $.noop } ).get( 'url' ) || ''; }, /** * Send data to the server using a HTTP POST request * * @param {String} requestId This is a unique id for the request * @param {{}} settings A set of key/value pairs that configure the Ajax request */ ajax: function( requestId, settings ) { const self = this; if ( ! requestId || $.isEmptyObject( settings ) ) { return; } // Ajax settings settings = $ush.clone( settings, /* default */{ data: {}, // data to be sent to the server abort: $.noop, // function to be called if the request abort complete: $.noop, // function to be called when the request finishes (after success and error callbacks are executed) error: $.noop, // function that will be called if an error occurs in the request success: $.noop // function to be called if the request succeeds } ); // Abort prev request if ( ! $ush.isUndefined( _$tmp.xhr[ requestId ] ) ) { _$tmp.xhr[ requestId ].abort(); if ( typeof settings.abort === 'function' ) { settings.abort.call( self, requestId ); } } /** * @see https://api.jquery.com/jquery.ajax */ _$tmp.xhr[ requestId ] = $.ajax({ data: $.extend( {}, self.config( 'ajaxArgs', {} ), settings.data ), dataType: 'json', type: 'post', url: _window.ajaxurl, cache: false, success: ( res ) => { delete _$tmp.xhr[ requestId ]; // Standard output of notifications and errors. if ( ! res.success && $ush.isUndefined( res.data.usb_ignore_standard_notify ) ) { var message = ( $ush.isUndefined( res.data ) ? 'Invalid `res.data`' : res.data.message ); self.notify.add( 'XHR: ' + message, NOTIFY_TYPE.ERROR ); } if ( typeof settings.success === 'function' ) { settings.success.call( self, res ); } }, error: ( jqXHR, textStatus, errorThrown ) => { if ( textStatus === 'abort' ) { return; } if ( typeof settings.error === 'function' ) { settings.error.call( self, requestId ); } // The showing request jqXHR errors if ( ! errorThrown ) { errorThrown = 'Request was not sent'; } self.log( 'XHR.error:' + errorThrown, jqXHR, requestId ); }, complete: ( _, textStatus ) => { if ( textStatus === 'abort' ) { return; } if ( typeof settings.complete === 'function' ) { settings.complete.call( self, requestId, textStatus ); } } }); }, /** * Redirects to another page * * @param {String} url The path or URL to redirect to */ redirect: function( url ) { const self = this; if ( url && typeof url === 'string' ) { self.iframeIsReady = false; _window.location.replace( url ); } } } ); // Export API _window.$usb = new USBInit( /* main container */'#usb-wrapper' ); }( jQuery );