Snapshot: MLS sync fixes, image refresh, plugin/theme updates
MLS plugin fixes from this session: - Fix silent insert failures: location column NOT NULL was rejecting wpdb->insert calls, causing ~18k new properties since Dec 2025 to be lost. Inserts now build raw SQL with ST_PointFromText so the spatial column is populated atomically. - Auto-refresh expired media URLs in MLS_Media_Handler::fetch_and_cache(), guarded by a property-level GET_LOCK so concurrent fetches share one API refresh. - Normalize WP_Error to null in mls_get_property_image() so callers can rely on the documented string|null contract. - Support comma-separated property_type filters in MLS_Query and MLS_Cluster so the homepage "View All Commercial" link (?property_type=Commercial+Sale,Land,Farm) actually filters correctly. - Incremental sync now looks back 10 minutes past the latest modification timestamp as a safety margin against missed records. - Smart sync exits silently (info-level, not warning) when a full sync is in progress. Operational: - New cron: weekly full sync Sundays at 3 AM (/usr/local/bin/mls-full-sync). - New cron: hourly 2GB cap on mls-thumbnails/ and cache/transformed-images/ (/usr/local/bin/mls-image-cache-cap). - Logrotate config for wp-content/debug.log (2-day retention, daily rotation, delaycompress). Repo policy: - CLAUDE.md updated with explicit "commit everything except build artifacts" policy. - .gitignore: untrack runtime image caches and debug.log rotations. Other modifications in this snapshot are pre-existing in-flight theme/plugin/db_content_updates work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+170
@@ -0,0 +1,170 @@
|
||||
/* global wpforms_ai_chat_element */
|
||||
|
||||
/**
|
||||
* @param wpforms_ai_chat_element.ajaxurl
|
||||
* @param wpforms_ai_chat_element.errors.network
|
||||
* @param wpforms_ai_chat_element.errors.default
|
||||
*/
|
||||
|
||||
/**
|
||||
* The WPForms AI API wrapper.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @return {Function} The app cloning function.
|
||||
*/
|
||||
export default function() { // eslint-disable-line no-unused-vars, max-lines-per-function
|
||||
/**
|
||||
* Public functions and properties.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const app = {
|
||||
/**
|
||||
* AI chat mode.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
mode: '',
|
||||
|
||||
/**
|
||||
* AI AJAX actions.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
actions: {
|
||||
rate: 'wpforms_rate_ai_response',
|
||||
choices: 'wpforms_get_ai_choices',
|
||||
forms: 'wpforms_get_ai_form',
|
||||
},
|
||||
|
||||
/**
|
||||
* AJAX request.
|
||||
*
|
||||
* @param {Object} data Data to send.
|
||||
*
|
||||
* @return {Promise} The fetch result data promise.
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
async ajax( data ) {
|
||||
if ( ! data.nonce ) {
|
||||
data.nonce = wpforms_ai_chat_element.nonce;
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams( data ).toString(),
|
||||
};
|
||||
|
||||
const response = await fetch( wpforms_ai_chat_element.ajaxurl, options )
|
||||
.catch( ( error ) => {
|
||||
if ( error.message === 'Failed to fetch' ) {
|
||||
throw new Error( wpforms_ai_chat_element.errors.network );
|
||||
} else {
|
||||
throw new Error( error.message );
|
||||
}
|
||||
} );
|
||||
|
||||
if ( ! response.ok ) {
|
||||
throw new Error( wpforms_ai_chat_element.errors.network );
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if ( ! result.success || result.data?.error ) {
|
||||
throw new Error(
|
||||
result.data?.error ?? wpforms_ai_chat_element.errors.default,
|
||||
{
|
||||
cause: result.data?.code ?? 400,
|
||||
} );
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prompt.
|
||||
*
|
||||
* @param {string} prompt The question to ask.
|
||||
* @param {string} sessionId Session ID.
|
||||
*
|
||||
* @return {Promise} The response data in promise.
|
||||
*/
|
||||
async prompt( prompt, sessionId ) {
|
||||
const data = {
|
||||
action: app.actions[ this.mode ] ?? app.actions.choices,
|
||||
prompt,
|
||||
};
|
||||
|
||||
if ( sessionId ) {
|
||||
data.session_id = sessionId; // eslint-disable-line camelcase
|
||||
}
|
||||
|
||||
return app.ajax( data );
|
||||
},
|
||||
|
||||
/**
|
||||
* Rate.
|
||||
*
|
||||
* @param {boolean} helpful Whether the response was helpful or not.
|
||||
* @param {string} responseId Response ID.
|
||||
*
|
||||
* @return {Promise} The response data in promise.
|
||||
*/
|
||||
async rate( helpful, responseId ) {
|
||||
const data = {
|
||||
action: app.actions.rate,
|
||||
helpful,
|
||||
response_id: responseId, // eslint-disable-line camelcase
|
||||
};
|
||||
|
||||
return app.ajax( data );
|
||||
},
|
||||
|
||||
setUp() {
|
||||
app.actions = {
|
||||
...app.actions,
|
||||
...wpforms_ai_chat_element.actions,
|
||||
};
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the AI chat mode.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {string} mode The mode to set.
|
||||
*
|
||||
* @return {Object} The app object.
|
||||
*/
|
||||
setMode( mode ) {
|
||||
this.mode = mode;
|
||||
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a clone of an app object.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {string} mode The AI prompt mode.
|
||||
*
|
||||
* @return {Object} Cloned app object.
|
||||
*/
|
||||
return function( mode ) {
|
||||
const obj = { ...app };
|
||||
|
||||
return obj.setUp().setMode( mode );
|
||||
};
|
||||
}
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
export default function(){let t={mode:"",actions:{rate:"wpforms_rate_ai_response",choices:"wpforms_get_ai_choices",forms:"wpforms_get_ai_form"},async ajax(e){e.nonce||(e.nonce=wpforms_ai_chat_element.nonce);e={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(e).toString()},e=await fetch(wpforms_ai_chat_element.ajaxurl,e).catch(e=>{throw"Failed to fetch"===e.message?new Error(wpforms_ai_chat_element.errors.network):new Error(e.message)});if(!e.ok)throw new Error(wpforms_ai_chat_element.errors.network);e=await e.json();if(!e.success||e.data?.error)throw new Error(e.data?.error??wpforms_ai_chat_element.errors.default,{cause:e.data?.code??400});return e.data},async prompt(e,r){e={action:t.actions[this.mode]??t.actions.choices,prompt:e};return r&&(e.session_id=r),t.ajax(e)},async rate(e,r){e={action:t.actions.rate,helpful:e,response_id:r};return t.ajax(e)},setUp(){return t.actions={...t.actions,...wpforms_ai_chat_element.actions},this},setMode(e){return this.mode=e,this}};return function(e){return{...t}.setUp().setMode(e)}}
|
||||
Executable
+319
@@ -0,0 +1,319 @@
|
||||
/* 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 );
|
||||
|
||||
// Toggle to the field.
|
||||
jQuery( `#wpforms-field-${ chat.fieldId }` ).click().promise().done( function() {
|
||||
jQuery( `#wpforms-field-option-basic-${ chat.fieldId } a.wpforms-field-option-group-toggle` ).click();
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
};
|
||||
}
|
||||
Vendored
Executable
+19
@@ -0,0 +1,19 @@
|
||||
export default function(c){return{getAnswer(e){if(e.choices?.length<1)return"";var t,r=[];for(t in e.choices)r.push(`
|
||||
<li class="wpforms-ai-chat-choices-item">
|
||||
${c.htmlSpecialChars(e.choices[t])}
|
||||
</li>
|
||||
`);let i=`
|
||||
<h4>${c.htmlSpecialChars(e.heading??"")}</h4>
|
||||
<ol>
|
||||
${r.join("")}
|
||||
</ol>
|
||||
`;return c.sessionId||(i+=`<span>${c.modeStrings.footer}</span>`),i},getAnswerButtonsPre(){return`
|
||||
<button type="button" class="wpforms-ai-chat-choices-insert wpforms-ai-chat-answer-action wpforms-btn-sm wpforms-btn-orange" >
|
||||
<span>${c.modeStrings.insert}</span>
|
||||
</button>
|
||||
`},getWarningMessage(){return c.triggerEvent("wpformsAIModalBeforeWarningMessageInsert",{fieldId:c.fieldId}),`<div class="wpforms-ai-chat-divider"></div>
|
||||
<div class="wpforms-chat-item-notice">
|
||||
<div class="wpforms-chat-item-notice-content">
|
||||
<span>${c.modeStrings.warning}</span>
|
||||
</div>
|
||||
</div>`},isWelcomeScreen(){var t=document.getElementById(`wpforms-field-option-row-${c.fieldId}-choices`).querySelectorAll("li input.label");if(1!==t.length||t[0].value.trim()){if(3<t.length)return!1;var r=Object.values(c.modeStrings.defaults);for(let e=0;e<t.length;e++)if(!r.includes(t[e].value))return!1}return!0},addedAnswer(e){e.querySelector(".wpforms-ai-chat-choices-insert")?.addEventListener("click",this.insertButtonClick.bind(this))},sanitizeResponse(t){if(Array.isArray(t?.choices)){let e=t.choices;e=e.map(e=>wpf.sanitizeHTML(e,wpforms_builder.allowed_label_html_tags)),t.choices=e.filter(e=>""!==e.trim())}return t},hasProhibitedCode(e,t){return t?.choices?.length!==e?.choices?.length},insertButtonClick(e){var t,e=e.target.closest(".wpforms-chat-item.wpforms-chat-item-choices"),r=e?.getAttribute("data-response-id"),i=(e?.querySelector("ol")).querySelectorAll(".wpforms-ai-chat-choices-item"),o=[];for(t in i)i.hasOwnProperty(t)&&i[t].textContent&&o.push(i[t].textContent.trim());c.wpformsAiApi.rate(!0,r),this.replaceChoices(o),jQuery("#wpforms-field-"+c.fieldId).click().promise().done(function(){jQuery(`#wpforms-field-option-basic-${c.fieldId} a.wpforms-field-option-group-toggle`).click()})},replaceChoices(e){var t,r=document.getElementById(`wpforms-field-option-row-${c.fieldId}-choices`).querySelector("ul.choices-list"),i=r.querySelector("li:first-child").cloneNode(!0);for(t in i.innerHTML=i.innerHTML.replace(/\[choices\]\[\d+\]/g,"[choices][{{key}}]"),r.innerHTML="",e){var o=(Number(t)+1).toString(),l=e[t],s=i.cloneNode(!0),s=this.getUpdatedSingleChoiceItem(s,o,l);r.appendChild(s)}r.setAttribute("data-next-id",e.length+1);var n=document.getElementById("wpforms-field-option-"+c.fieldId).querySelector("input.wpforms-field-option-hidden-type")?.value;WPFormsBuilder.fieldChoiceUpdate(n,c.fieldId,e.length),WPFormsBuilder.triggerBuilderEvent("wpformsFieldChoiceAdd"),c.triggerEvent("wpformsAIModalAfterChoicesInsert",{fieldId:c.fieldId})},getUpdatedSingleChoiceItem(e,t,r){e.setAttribute("data-key",t.toString()),e.innerHTML=e.innerHTML.replaceAll("{{key}}",t),r=wpf.sanitizeHTML(r);e.querySelector("input.default").removeAttribute("checked");t=e.querySelector("input.label"),t.value=r,t.setAttribute("value",r),t=e.querySelector("input.value"),t.value=r,t.setAttribute("value",r),t=e.querySelector(".wpforms-image-upload"),r=t.querySelector("input.source"),r.value="",r.setAttribute("value",""),t.querySelector(".preview").innerHTML="",t.querySelector(".wpforms-image-upload-add").style.display="block",r=e.querySelector(".wpforms-icon-select");return r.querySelector(".ic-fa-preview").setAttribute("class","ic-fa-preview ic-fa-regular ic-fa-face-smile"),r.querySelector("input.source-icon").value="face-smile",r.querySelector("input.source-icon-style").value="regular",e}}}
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
/* global WPFormsAIChatHTMLElement, WPFormsAIFormGenerator, wpf, wpforms_builder */
|
||||
|
||||
/**
|
||||
* @param chat.modeStrings.footerFirst
|
||||
* @param chat.modeStrings.inactiveAnswerTitle
|
||||
* @param chat.preventResizeInput
|
||||
* @param response.form_title
|
||||
* @param wpforms_builder.allowed_label_html_tags
|
||||
*/
|
||||
|
||||
/**
|
||||
* The WPForms AI chat element.
|
||||
*
|
||||
* Forms mode helpers module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {WPFormsAIChatHTMLElement} chat The chat element.
|
||||
*
|
||||
* @return {Object} Forms helpers object.
|
||||
*/
|
||||
export default function( chat ) { // eslint-disable-line no-unused-vars, max-lines-per-function
|
||||
/**
|
||||
* The default `forms` mode helpers object.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
const forms = {
|
||||
/**
|
||||
* Init `forms` mode.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
init() {
|
||||
// Set the initial form generator state.
|
||||
if ( chat.sessionId ) {
|
||||
WPFormsAIFormGenerator.state.chatStart = true;
|
||||
|
||||
// Remove the selected state from the current template card.
|
||||
WPFormsAIFormGenerator.main.el.$templateCard
|
||||
.next( '.selected' ).removeClass( 'selected' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the message input field.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
resetInput() {
|
||||
chat.resizeInput();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the answer based on AI response data.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} response The AI response data.
|
||||
*
|
||||
* @return {string} HTML markup.
|
||||
*/
|
||||
getAnswer( response ) {
|
||||
if ( ! response ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const rnd = Math.floor( Math.random() * chat.modeStrings.footer.length );
|
||||
const footer = chat.modeStrings.footer[ rnd ];
|
||||
const answer = response.explanation || ( response.form_title ?? '' );
|
||||
|
||||
return `
|
||||
<h4>${ answer }</h4>
|
||||
<span>${ footer }</span>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the answer pre-buttons HTML markup.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return {string} The answer pre-buttons HTML markup.
|
||||
*/
|
||||
getAnswerButtonsPre() {
|
||||
return `
|
||||
<button type="button" class="wpforms-ai-chat-use-form wpforms-ai-chat-answer-action wpforms-btn-sm wpforms-btn-orange" >
|
||||
<span>${ chat.modeStrings.useForm }</span>
|
||||
</button>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* The answer was added.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {HTMLElement} element The answer element.
|
||||
*/
|
||||
addedAnswer( element ) { // eslint-disable-line no-unused-vars
|
||||
forms.updateInactiveAnswers();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set active answer.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {HTMLElement} element The answer element.
|
||||
*/
|
||||
setActiveAnswer( element ) {
|
||||
forms.updateInactiveAnswers();
|
||||
|
||||
element.querySelector( '.wpforms-chat-item-content' ).setAttribute( 'title', '' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Update inactive answers.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
updateInactiveAnswers() {
|
||||
chat.messageList.querySelectorAll( '.wpforms-chat-item-answer:not(.active) .wpforms-chat-item-content' )
|
||||
.forEach( ( el ) => {
|
||||
// Set title attribute for inactive answers.
|
||||
el.setAttribute( 'title', chat.modeStrings.inactiveAnswerTitle );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether the Welcome Screen should be displayed.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return {boolean} Display the Welcome Screen or not.
|
||||
*/
|
||||
isWelcomeScreen() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitize response.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} response The response data to sanitize.
|
||||
*
|
||||
* @return {Object} The sanitized response.
|
||||
*/
|
||||
sanitizeResponse( response ) {
|
||||
if ( ! response.explanation ) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Sanitize explanation string.
|
||||
response.explanation = wpf.sanitizeHTML( response.explanation, wpforms_builder.allowed_label_html_tags );
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
return forms;
|
||||
}
|
||||
Vendored
Executable
+8
@@ -0,0 +1,8 @@
|
||||
export default function(r){let t={init(){r.sessionId&&(WPFormsAIFormGenerator.state.chatStart=!0,WPFormsAIFormGenerator.main.el.$templateCard.next(".selected").removeClass("selected"))},resetInput(){r.resizeInput()},getAnswer(e){var t;return e?(t=Math.floor(Math.random()*r.modeStrings.footer.length),t=r.modeStrings.footer[t],`
|
||||
<h4>${e.explanation||(e.form_title??"")}</h4>
|
||||
<span>${t}</span>
|
||||
`):""},getAnswerButtonsPre(){return`
|
||||
<button type="button" class="wpforms-ai-chat-use-form wpforms-ai-chat-answer-action wpforms-btn-sm wpforms-btn-orange" >
|
||||
<span>${r.modeStrings.useForm}</span>
|
||||
</button>
|
||||
`},addedAnswer(e){t.updateInactiveAnswers()},setActiveAnswer(e){t.updateInactiveAnswers(),e.querySelector(".wpforms-chat-item-content").setAttribute("title","")},updateInactiveAnswers(){r.messageList.querySelectorAll(".wpforms-chat-item-answer:not(.active) .wpforms-chat-item-content").forEach(e=>{e.setAttribute("title",r.modeStrings.inactiveAnswerTitle)})},isWelcomeScreen(){return!0},sanitizeResponse(e){return e.explanation&&(e.explanation=wpf.sanitizeHTML(e.explanation,wpforms_builder.allowed_label_html_tags)),e}};return t}
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
/* global WPFormsAIChatHTMLElement */
|
||||
|
||||
/**
|
||||
* The WPForms AI chat element.
|
||||
*
|
||||
* Choices helpers module.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {WPFormsAIChatHTMLElement} chat The chat element.
|
||||
*
|
||||
* @return {Function} The app cloning function.
|
||||
*/
|
||||
export default function( chat ) { // eslint-disable-line no-unused-vars
|
||||
/**
|
||||
* The default `text` mode helpers object.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
return {
|
||||
/**
|
||||
* Get the `text` answer based on AI response data.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Object} response The AI response data.
|
||||
*
|
||||
* @return {string} HTML markup.
|
||||
*/
|
||||
getAnswer( response ) {
|
||||
return `
|
||||
<h4>${ response?.heading ?? '' }</h4>
|
||||
<p>${ response?.text ?? '' }</p>
|
||||
<span>${ response?.footer ?? '' }</span>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the answer pre-buttons HTML markup.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @return {string} The answer pre-buttons HTML markup.
|
||||
*/
|
||||
getAnswerButtonsPre() {
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Added answer callback.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
addedAnswer() {},
|
||||
|
||||
/**
|
||||
* Determine whether the Welcome Screen should be displayed.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return {boolean} Display the Welcome Screen or not.
|
||||
*/
|
||||
isWelcomeScreen() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
Vendored
Executable
+5
@@ -0,0 +1,5 @@
|
||||
export default function(e){return{getAnswer(e){return`
|
||||
<h4>${e?.heading??""}</h4>
|
||||
<p>${e?.text??""}</p>
|
||||
<span>${e?.footer??""}</span>
|
||||
`},getAnswerButtonsPre(){return""},addedAnswer(){},isWelcomeScreen(){return!0}}}
|
||||
Executable
+1398
File diff suppressed because it is too large
Load Diff
Vendored
Executable
+43
File diff suppressed because one or more lines are too long
Executable
+245
@@ -0,0 +1,245 @@
|
||||
/* global wpforms_ai_chat_element */
|
||||
|
||||
/**
|
||||
* @param wpforms_ai_chat_element.pinChat
|
||||
* @param wpforms_ai_chat_element.unpinChat
|
||||
* @param wpforms_ai_chat_element.close
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dock JS module.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {jQuery} $ jQuery object.
|
||||
*
|
||||
* @return {Object} Dock object.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const wpFormsAIDock = ( function( $ ) {
|
||||
/**
|
||||
* Pin modal.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {jQuery} $modal Modal element.
|
||||
*/
|
||||
function pinModal( $modal ) {
|
||||
localStorage.setItem( 'wpforms-ai-chat-prefers-pinned', '1' );
|
||||
|
||||
$modal.find( '.wpforms-ai-modal-pin' ).attr( 'title', wpforms_ai_chat_element.unpinChat );
|
||||
|
||||
const $toolbar = $( '#wpforms-builder-form .wpforms-toolbar' );
|
||||
|
||||
// Get the distance from the top of the screen to the bottom border of the toolbar.
|
||||
const toolbarHeight = $toolbar.offset().top + $toolbar.outerHeight();
|
||||
|
||||
$modal.addClass( 'pinned' );
|
||||
|
||||
if ( $( '#wpadminbar' ).is( ':visible' ) ) {
|
||||
$modal.addClass( 'with-wpadminbar' );
|
||||
}
|
||||
|
||||
$modal.insertAfter( $toolbar ).promise().done( function() {
|
||||
$modal.css( {
|
||||
top: toolbarHeight,
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpin modal.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {jQuery} $modal Modal element.
|
||||
*/
|
||||
function unPinModal( $modal ) {
|
||||
localStorage.setItem( 'wpforms-ai-chat-prefers-pinned', '0' );
|
||||
|
||||
$modal.find( '.wpforms-ai-modal-pin' ).attr( 'title', wpforms_ai_chat_element.pinChat );
|
||||
|
||||
$modal.removeClass( 'pinned' );
|
||||
$modal.removeClass( 'with-wpadminbar' );
|
||||
|
||||
$modal.appendTo( $( 'body' ) ).promise().done( function() {
|
||||
$modal.css( {
|
||||
top: 0,
|
||||
} );
|
||||
} );
|
||||
|
||||
$modal.find( '.wpforms-ai-modal-top-bar' ).removeClass( 'scrolled' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click on the pin button.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*/
|
||||
function onPinIconClick() {
|
||||
if ( $( this ).hasClass( 'not-allowed' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $modal = $( this ).closest( '.jconfirm.jconfirm-wpforms-ai-modal' );
|
||||
|
||||
if ( $modal.hasClass( 'pinned' ) ) {
|
||||
unPinModal( $modal );
|
||||
} else {
|
||||
pinModal( $modal );
|
||||
}
|
||||
|
||||
// Re-apply this action also for other modals but hide them.
|
||||
const $otherModals = $( '.jconfirm.jconfirm-wpforms-ai-modal' ).not( $modal );
|
||||
|
||||
$otherModals.each( function() {
|
||||
const $otherModal = $( this );
|
||||
|
||||
if ( $otherModal.hasClass( 'pinned' ) ) {
|
||||
unPinModal( $otherModal );
|
||||
} else {
|
||||
pinModal( $otherModal );
|
||||
}
|
||||
|
||||
$otherModal.hide();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click on the panel toggle button.
|
||||
*
|
||||
* Hide pinned chats if this is not the fields panel.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {Object} clicked Clicked a button object.
|
||||
*/
|
||||
function onPanelToggleClick( clicked ) {
|
||||
const $this = $( clicked.target ),
|
||||
dataPanel = $this.closest( 'button' ).data( 'panel' );
|
||||
|
||||
if ( dataPanel === 'fields' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$( '.jconfirm.jconfirm-wpforms-ai-modal.pinned' ).each( function() {
|
||||
$( this ).hide();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind events.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*/
|
||||
function bindEvents() {
|
||||
const AIModalPin = $( '.wpforms-ai-modal-pin' );
|
||||
|
||||
$( document )
|
||||
.off( 'click', '.wpforms-ai-modal-pin' )
|
||||
.on( 'click', '.wpforms-ai-modal-pin', onPinIconClick )
|
||||
// Hide pin icon for the time of response generation.
|
||||
.on( 'wpformsAIChatBeforeSendMessage', () => AIModalPin.addClass( 'not-allowed' ) )
|
||||
.on( 'wpformsAIChatBeforeError wpformsAIChatAfterTypeText', () => AIModalPin.removeClass( 'not-allowed' ) );
|
||||
|
||||
$( '#wpforms-panels-toggle button' )
|
||||
.off( 'click', onPanelToggleClick )
|
||||
.on( 'click', onPanelToggleClick );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the modal element.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {number} fieldId Field ID.
|
||||
*
|
||||
* @return {jQuery} Modal element.
|
||||
*/
|
||||
function getModal( fieldId ) {
|
||||
const $chatElement = $( 'wpforms-ai-chat[field-id="' + fieldId + '"]' );
|
||||
|
||||
return $chatElement.closest( '.jconfirm.jconfirm-wpforms-ai-modal' ).last();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare dock by adding pin button.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {number} fieldId Field ID.
|
||||
*/
|
||||
function prepareDock( fieldId ) {
|
||||
const $jconfirmAIModal = getModal( fieldId ),
|
||||
dockAlreadyPrepared = $jconfirmAIModal.find( '.wpforms-ai-modal-pin' ).length;
|
||||
|
||||
if ( dockAlreadyPrepared ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $closeIcon = $jconfirmAIModal.find( '.jconfirm-closeIcon' );
|
||||
|
||||
// Append new bar after close icon and move close icon inside this bar.
|
||||
$closeIcon.after(
|
||||
`<div class="wpforms-ai-modal-top-bar">
|
||||
<div class="wpforms-ai-modal-pin" title="${ wpforms_ai_chat_element.pinChat }"></div>
|
||||
</div>`
|
||||
).promise().done( function() {
|
||||
const $topBar = $jconfirmAIModal.find( '.wpforms-ai-modal-top-bar' );
|
||||
|
||||
$closeIcon.appendTo( $topBar );
|
||||
} );
|
||||
|
||||
$closeIcon.attr( 'title', wpforms_ai_chat_element.close );
|
||||
|
||||
const $topBar = $jconfirmAIModal.find( '.wpforms-ai-modal-top-bar' ),
|
||||
$messageList = $jconfirmAIModal.find( '.wpforms-ai-chat-message-list' );
|
||||
|
||||
$messageList.off( 'scroll' );
|
||||
$messageList.on( 'scroll', function() {
|
||||
if ( $messageList.scrollTop() > 0 ) {
|
||||
$topBar.addClass( 'scrolled' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$topBar.removeClass( 'scrolled' );
|
||||
} );
|
||||
|
||||
$jconfirmAIModal.on( 'remove', function() {
|
||||
$messageList.off( 'scroll' );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe pin modal on open.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {string} fieldId Field ID.
|
||||
*/
|
||||
function onOpen( fieldId ) {
|
||||
const savedState = localStorage.getItem( 'wpforms-ai-chat-prefers-pinned' ) || '0';
|
||||
|
||||
if ( savedState === '0' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
pinModal( getModal( fieldId ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the dock.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {number} fieldId Field ID.
|
||||
*/
|
||||
function init( fieldId ) {
|
||||
prepareDock( fieldId );
|
||||
bindEvents();
|
||||
onOpen( fieldId );
|
||||
}
|
||||
|
||||
return { init };
|
||||
}( jQuery ) );
|
||||
Vendored
Executable
+3
@@ -0,0 +1,3 @@
|
||||
let wpFormsAIDock=(e=>{function s(o){localStorage.setItem("wpforms-ai-chat-prefers-pinned","1"),o.find(".wpforms-ai-modal-pin").attr("title",wpforms_ai_chat_element.unpinChat);var a=e("#wpforms-builder-form .wpforms-toolbar");let i=a.offset().top+a.outerHeight();o.addClass("pinned"),e("#wpadminbar").is(":visible")&&o.addClass("with-wpadminbar"),o.insertAfter(a).promise().done(function(){o.css({top:i})})}function a(o){localStorage.setItem("wpforms-ai-chat-prefers-pinned","0"),o.find(".wpforms-ai-modal-pin").attr("title",wpforms_ai_chat_element.pinChat),o.removeClass("pinned"),o.removeClass("with-wpadminbar"),o.appendTo(e("body")).promise().done(function(){o.css({top:0})}),o.find(".wpforms-ai-modal-top-bar").removeClass("scrolled")}function n(){var o;e(this).hasClass("not-allowed")||(((o=e(this).closest(".jconfirm.jconfirm-wpforms-ai-modal")).hasClass("pinned")?a:s)(o),e(".jconfirm.jconfirm-wpforms-ai-modal").not(o).each(function(){var o=e(this);(o.hasClass("pinned")?a:s)(o),o.hide()}))}function t(o){"fields"!==e(o.target).closest("button").data("panel")&&e(".jconfirm.jconfirm-wpforms-ai-modal.pinned").each(function(){e(this).hide()})}function r(o){return e('wpforms-ai-chat[field-id="'+o+'"]').closest(".jconfirm.jconfirm-wpforms-ai-modal").last()}return{init:function(a){{var i=a;let e=r(i),o=e.find(".wpforms-ai-modal-pin").length;if(!o){let a=e.find(".jconfirm-closeIcon"),o=(a.after(`<div class="wpforms-ai-modal-top-bar">
|
||||
<div class="wpforms-ai-modal-pin" title="${wpforms_ai_chat_element.pinChat}"></div>
|
||||
</div>`).promise().done(function(){var o=e.find(".wpforms-ai-modal-top-bar");a.appendTo(o)}),a.attr("title",wpforms_ai_chat_element.close),e.find(".wpforms-ai-modal-top-bar")),i=e.find(".wpforms-ai-chat-message-list");i.off("scroll"),i.on("scroll",function(){0<i.scrollTop()?o.addClass("scrolled"):o.removeClass("scrolled")}),e.on("remove",function(){i.off("scroll")})}}{let o=e(".wpforms-ai-modal-pin");e(document).off("click",".wpforms-ai-modal-pin").on("click",".wpforms-ai-modal-pin",n).on("wpformsAIChatBeforeSendMessage",()=>o.addClass("not-allowed")).on("wpformsAIChatBeforeError wpformsAIChatAfterTypeText",()=>o.removeClass("not-allowed")),e("#wpforms-panels-toggle button").off("click",t).on("click",t)}i=a,"0"!==(localStorage.getItem("wpforms-ai-chat-prefers-pinned")||"0")&&s(r(i))}}})(jQuery);
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
/* global wpforms_ai_chat_element, wpFormsAIDock */
|
||||
|
||||
// noinspection ES6ConvertVarToLetConst
|
||||
/**
|
||||
* AI modal.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
var WPFormsAIModal = window.WPFormsAIModal || ( function( document, window, $ ) {
|
||||
/**
|
||||
* Public functions and properties.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const app = {
|
||||
/**
|
||||
* Default modal options.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
defaultOptions: {
|
||||
title: false,
|
||||
content: '',
|
||||
type: 'ai',
|
||||
smoothContent: true,
|
||||
bgOpacity: 1,
|
||||
boxWidth: 650,
|
||||
contentMaxHeight: 600,
|
||||
closeIcon: true,
|
||||
buttons: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the engine.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
init() {
|
||||
$( app.ready );
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialized once the DOM is fully loaded.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
ready() {
|
||||
app.extendJqueryConfirm();
|
||||
app.bindChoicesActions();
|
||||
},
|
||||
|
||||
/**
|
||||
* Process various events for choices modal.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
bindChoicesActions() {
|
||||
$( document )
|
||||
.on( 'click', '.wpforms-ai-choices-button', app.initChoicesModal )
|
||||
.on( 'wpformsAIChatBeforeRefreshConfirm', app.beforeChoicesRefreshConfirm )
|
||||
.on( 'wpformsAIModalBeforeWarningMessageInsert', app.refreshModalHeight )
|
||||
.on( 'wpformsAIChatAfterRefresh', app.refreshModalHeight )
|
||||
.on( 'wpformsAIChatCancelRefresh', app.cancelChoicesRefresh )
|
||||
.on( 'wpformsAIChatBeforeSendMessage', function( e ) {
|
||||
app.resizeModalHeight( e.detail.fieldId );
|
||||
} )
|
||||
.on( 'wpformsAIChatAfterAddAnswer', function( e ) {
|
||||
app.resizeModalHeight( e.detail.fieldId );
|
||||
} )
|
||||
.on( 'wpformsAIModalAfterChoicesInsert', function( e ) {
|
||||
app.hideChoicesModal( e.detail.fieldId );
|
||||
} );
|
||||
|
||||
$( window ).on( 'resize', function() {
|
||||
$( '.jconfirm-wpforms-ai-modal wpforms-ai-chat' ).each( function() {
|
||||
app.resizeModalHeight( $( this ).attr( 'field-id' ) );
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Object} args Modal window arguments.
|
||||
*/
|
||||
initModal( args ) {
|
||||
// Open the modal window.
|
||||
$.confirm( { ...app.defaultOptions, ...args } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init choices modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
initChoicesModal() {
|
||||
const $button = $( this );
|
||||
|
||||
if ( $button.hasClass( 'wpforms-prevent-default' ) ) {
|
||||
$button.trigger( 'blur' );
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldId = $button.data( 'field-id' ),
|
||||
$modal = $( `.jconfirm-wpforms-ai-modal-choices-${ fieldId }` );
|
||||
|
||||
// Close any other modals.
|
||||
$( `.jconfirm-wpforms-ai-modal:not(.jconfirm-wpforms-ai-modal-choices-${ fieldId })` )
|
||||
.addClass( 'wpforms-hidden' )
|
||||
.fadeOut();
|
||||
|
||||
if ( $modal.length ) {
|
||||
$modal.removeClass( 'wpforms-hidden' ).fadeIn();
|
||||
return;
|
||||
}
|
||||
|
||||
const args = {},
|
||||
hideChoices = function() {
|
||||
app.hideChoicesModal( fieldId );
|
||||
return false;
|
||||
};
|
||||
|
||||
args.content = `<wpforms-ai-chat mode="choices" field-id="${ fieldId }" />`;
|
||||
args.theme = `wpforms-ai-modal, wpforms-ai-purple, wpforms-ai-modal-choices-${ fieldId }`;
|
||||
args.backgroundDismiss = hideChoices;
|
||||
args.backgroundDismissAnimation = '';
|
||||
args.contentMaxHeight = Math.min( app.defaultOptions.contentMaxHeight, app.getMaxModalHeight() );
|
||||
args.onOpen = function() {
|
||||
// Unbind the click event from the close icon and use our own instead.
|
||||
this.$closeIcon.off( 'click' );
|
||||
this.$closeIcon.on( 'click', hideChoices );
|
||||
};
|
||||
args.onOpenBefore = function() {
|
||||
wpFormsAIDock.init( fieldId );
|
||||
};
|
||||
|
||||
app.initModal( args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the choices modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {string} fieldId Choice field ID.
|
||||
*/
|
||||
hideChoicesModal( fieldId ) {
|
||||
$( `.jconfirm-wpforms-ai-modal-choices-${ fieldId }` ).addClass( 'wpforms-hidden' ).fadeOut();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the choices modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {string} fieldId Choice field ID.
|
||||
*/
|
||||
showChoicesModal( fieldId ) {
|
||||
$( `.jconfirm-wpforms-ai-modal-choices-${ fieldId }` ).removeClass( 'wpforms-hidden' ).fadeIn();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resize choices modal window height.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param {string} fieldId Field ID.
|
||||
*/
|
||||
resizeModalHeight( fieldId ) {
|
||||
const modalHeight = app.getMaxModalHeight();
|
||||
const $modal = $( '.jconfirm-wpforms-ai-modal' ).filter( function() {
|
||||
// find class starts with jconfirm-wpforms-ai-modal- and ends with -{fieldId}.
|
||||
return $( this ).attr( 'class' ).match( new RegExp( 'jconfirm-wpforms-ai-modal-.*-' + fieldId, 'i' ) );
|
||||
} );
|
||||
|
||||
$modal.find( '.jconfirm-content-pane' )
|
||||
.css( {
|
||||
height: modalHeight,
|
||||
'max-height': modalHeight,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Before choices refresh confirm is displayed.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Event} e Event object.
|
||||
*/
|
||||
beforeChoicesRefreshConfirm( e ) {
|
||||
const fieldId = e.detail?.fieldId || 0;
|
||||
|
||||
app.hideChoicesModal( fieldId );
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel choices' refresh.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Event} e Event object.
|
||||
*/
|
||||
cancelChoicesRefresh( e ) {
|
||||
const fieldId = e.detail?.fieldId || 0;
|
||||
|
||||
app.showChoicesModal( fieldId );
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the main modal window height.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Event} e Event object.
|
||||
*/
|
||||
refreshModalHeight( e ) {
|
||||
const fieldId = e.detail?.fieldId || 0;
|
||||
const maxHeight = Math.min( app.getMaxModalHeight(), app.defaultOptions.contentMaxHeight );
|
||||
|
||||
app.showChoicesModal( fieldId );
|
||||
|
||||
// Reset choices modal window height.
|
||||
$( `.jconfirm-wpforms-ai-modal-choices-${ fieldId } .jconfirm-content-pane` )
|
||||
.css( {
|
||||
height: maxHeight,
|
||||
'max-height': maxHeight,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the max modal height.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @return {number} The max modal height.
|
||||
*/
|
||||
getMaxModalHeight() {
|
||||
// 80% of the window height, but not more than 800 px.
|
||||
return Math.min( $( window ).height() * 0.8, 800 );
|
||||
},
|
||||
|
||||
/**
|
||||
* Extend jquery-confirm plugin with support of max-height for the content area.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
extendJqueryConfirm() {
|
||||
// Extend a method of global instance.
|
||||
window.Jconfirm.prototype._updateContentMaxHeight = function() {
|
||||
this.$contentPane.css( {
|
||||
'max-height': this.contentMaxHeight + 'px',
|
||||
} );
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm a modal window.
|
||||
*
|
||||
* This is a wrapper for the `jquery.confirm` plugin.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Object} args Modal window arguments.
|
||||
*/
|
||||
confirmModal( args ) {
|
||||
const options = {
|
||||
title: false,
|
||||
content: '',
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
type: 'orange',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: wpforms_ai_chat_element.btnYes,
|
||||
btnClass: 'btn-confirm',
|
||||
keys: [ 'enter' ],
|
||||
action() {
|
||||
if ( typeof args.onConfirm === 'function' ) {
|
||||
args.onConfirm();
|
||||
}
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
text: wpforms_ai_chat_element.btnCancel,
|
||||
action() {
|
||||
if ( typeof args.onCancel === 'function' ) {
|
||||
args.onCancel();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$.confirm( { ...options, ...args } );
|
||||
},
|
||||
};
|
||||
|
||||
// Provide access to public functions/properties.
|
||||
return app;
|
||||
}( document, window, jQuery ) );
|
||||
|
||||
// Initialize.
|
||||
WPFormsAIModal.init();
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
var WPFormsAIModal=window.WPFormsAIModal||((e,o,t)=>{let n={defaultOptions:{title:!1,content:"",type:"ai",smoothContent:!0,bgOpacity:1,boxWidth:650,contentMaxHeight:600,closeIcon:!0,buttons:!1},init(){t(n.ready)},ready(){n.extendJqueryConfirm(),n.bindChoicesActions()},bindChoicesActions(){t(e).on("click",".wpforms-ai-choices-button",n.initChoicesModal).on("wpformsAIChatBeforeRefreshConfirm",n.beforeChoicesRefreshConfirm).on("wpformsAIModalBeforeWarningMessageInsert",n.refreshModalHeight).on("wpformsAIChatAfterRefresh",n.refreshModalHeight).on("wpformsAIChatCancelRefresh",n.cancelChoicesRefresh).on("wpformsAIChatBeforeSendMessage",function(e){n.resizeModalHeight(e.detail.fieldId)}).on("wpformsAIChatAfterAddAnswer",function(e){n.resizeModalHeight(e.detail.fieldId)}).on("wpformsAIModalAfterChoicesInsert",function(e){n.hideChoicesModal(e.detail.fieldId)}),t(o).on("resize",function(){t(".jconfirm-wpforms-ai-modal wpforms-ai-chat").each(function(){n.resizeModalHeight(t(this).attr("field-id"))})})},initModal(e){t.confirm({...n.defaultOptions,...e})},initChoicesModal(){var o=t(this);if(o.hasClass("wpforms-prevent-default"))o.trigger("blur");else{let i=o.data("field-id"),e=t(".jconfirm-wpforms-ai-modal-choices-"+i);if(t(`.jconfirm-wpforms-ai-modal:not(.jconfirm-wpforms-ai-modal-choices-${i})`).addClass("wpforms-hidden").fadeOut(),e.length)e.removeClass("wpforms-hidden").fadeIn();else{let e={},o=function(){return n.hideChoicesModal(i),!1};e.content=`<wpforms-ai-chat mode="choices" field-id="${i}" />`,e.theme="wpforms-ai-modal, wpforms-ai-purple, wpforms-ai-modal-choices-"+i,e.backgroundDismiss=o,e.backgroundDismissAnimation="",e.contentMaxHeight=Math.min(n.defaultOptions.contentMaxHeight,n.getMaxModalHeight()),e.onOpen=function(){this.$closeIcon.off("click"),this.$closeIcon.on("click",o)},e.onOpenBefore=function(){wpFormsAIDock.init(i)},n.initModal(e)}}},hideChoicesModal(e){t(".jconfirm-wpforms-ai-modal-choices-"+e).addClass("wpforms-hidden").fadeOut()},showChoicesModal(e){t(".jconfirm-wpforms-ai-modal-choices-"+e).removeClass("wpforms-hidden").fadeIn()},resizeModalHeight(e){var o=n.getMaxModalHeight();t(".jconfirm-wpforms-ai-modal").filter(function(){return t(this).attr("class").match(new RegExp("jconfirm-wpforms-ai-modal-.*-"+e,"i"))}).find(".jconfirm-content-pane").css({height:o,"max-height":o})},beforeChoicesRefreshConfirm(e){e=e.detail?.fieldId||0;n.hideChoicesModal(e)},cancelChoicesRefresh(e){e=e.detail?.fieldId||0;n.showChoicesModal(e)},refreshModalHeight(e){var e=e.detail?.fieldId||0,o=Math.min(n.getMaxModalHeight(),n.defaultOptions.contentMaxHeight);n.showChoicesModal(e),t(`.jconfirm-wpforms-ai-modal-choices-${e} .jconfirm-content-pane`).css({height:o,"max-height":o})},getMaxModalHeight(){return Math.min(.8*t(o).height(),800)},extendJqueryConfirm(){o.Jconfirm.prototype._updateContentMaxHeight=function(){this.$contentPane.css({"max-height":this.contentMaxHeight+"px"})}},confirmModal(e){var o={title:!1,content:"",icon:"fa fa-exclamation-circle",type:"orange",buttons:{confirm:{text:wpforms_ai_chat_element.btnYes,btnClass:"btn-confirm",keys:["enter"],action(){"function"==typeof e.onConfirm&&e.onConfirm()}},cancel:{text:wpforms_ai_chat_element.btnCancel,action(){"function"==typeof e.onCancel&&e.onCancel()}}}};t.confirm({...o,...e})}};return n})(document,window,jQuery);WPFormsAIModal.init();
|
||||
Executable
+223
@@ -0,0 +1,223 @@
|
||||
/* global wpforms_ai_form_generator, wpf, wpforms_ai_chat_element, WPFormsBuilder */
|
||||
|
||||
/**
|
||||
* @param strings.dismissed.installAddons
|
||||
* @param strings.isLicenseActive
|
||||
* @param strings.modules.main
|
||||
* @param strings.templateCard.buttonTextInit
|
||||
* @param strings.templateCard.imageSrc
|
||||
* @param strings.liteConnectAllowed
|
||||
* @param strings.liteConnectEnabled
|
||||
* @param strings.liteConnectNotAllowed
|
||||
* @param window.WPFormsAIFormGenerator
|
||||
* @param wpforms_builder.is_ai_disabled
|
||||
*/
|
||||
|
||||
// noinspection ES6ConvertVarToLetConst
|
||||
/**
|
||||
* WPForms AI Form Generator.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
var WPFormsAIFormGenerator = window.WPFormsAIFormGenerator || ( function( document, window, $ ) {
|
||||
/**
|
||||
* Localized strings.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const strings = wpforms_ai_form_generator;
|
||||
|
||||
/**
|
||||
* Public functions and properties.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const app = {
|
||||
/**
|
||||
* State data holder.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
state: {},
|
||||
|
||||
/**
|
||||
* Main module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
main: null,
|
||||
|
||||
/**
|
||||
* The form preview module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
preview: null,
|
||||
|
||||
/**
|
||||
* Start the engine.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
init() {
|
||||
if ( window.wpforms_builder?.is_ai_disabled || app.isLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.updateLocationUrl();
|
||||
app.events();
|
||||
|
||||
app.isLoaded = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Events.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
events() {
|
||||
$( document )
|
||||
.on( 'wpformsSetupPanelBeforeInitTemplatesList', app.addTemplateCard );
|
||||
|
||||
$( '#wpforms-builder' )
|
||||
.on( 'wpformsBuilderReady', app.maybeSaveForm )
|
||||
.on( 'wpformsBuilderPanelLoaded', app.panelLoaded );
|
||||
},
|
||||
|
||||
/**
|
||||
* Panel loaded event.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
* @param {string} panel Panel name.
|
||||
*/
|
||||
panelLoaded( e, panel ) {
|
||||
if ( panel !== 'setup' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load generator modules and run the main module.
|
||||
Promise.all( [
|
||||
import( strings.modules.main ),
|
||||
import( strings.modules.preview ),
|
||||
import( strings.modules.modals ),
|
||||
] )
|
||||
.then( ( [ moduleMain, modulePreview, moduleModals ] ) => {
|
||||
app.main = moduleMain.default( app, $ );
|
||||
app.preview = modulePreview.default( app, $ );
|
||||
app.modals = moduleModals.default( app, $ );
|
||||
|
||||
// Run the main module.
|
||||
app.main.init();
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the generator template card to the list.
|
||||
*
|
||||
* At this point, before the list is rendered, we can add our card.
|
||||
* The card will be added to the top of the list.
|
||||
* Event handlers will be attached later by the main module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
addTemplateCard() {
|
||||
if ( $( '#wpforms-template-generate' ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$( '#wpforms-setup-templates-list .list' ).prepend( app.renderTemplateCard() );
|
||||
|
||||
wpf.initTooltips( $( '#wpforms-template-generate .wpforms-template-buttons' ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the template card HTML.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return {string} The card markup.
|
||||
*/
|
||||
renderTemplateCard() { // eslint-disable-line complexity
|
||||
const cardClass = window.wpforms_builder?.template_slug === 'generate' ? 'selected' : '';
|
||||
|
||||
let buttonAttr = '';
|
||||
let buttonClass = ! Object.keys( strings.addonsData ).length || strings.dismissed.installAddons
|
||||
? 'wpforms-template-generate'
|
||||
: 'wpforms-template-generate-install-addons';
|
||||
|
||||
// In Lite, we should disable the button in the case Lite Connect is not allowed.
|
||||
if ( ! strings.isPro && ! strings.liteConnectAllowed ) {
|
||||
buttonClass += ' wpforms-inactive wpforms-help-tooltip wpforms-prevent-default';
|
||||
buttonAttr = `data-tooltip-position="top" title="${ strings.templateCard.liteConnectNotAllowed }"`;
|
||||
}
|
||||
|
||||
// In Lite, we should show the modal to enable Lite Connect if it is allowed.
|
||||
if ( ! strings.isPro && ! strings.liteConnectEnabled && strings.liteConnectAllowed ) {
|
||||
buttonClass += ' enable-lite-connect-modal wpforms-prevent-default';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="wpforms-template ${ cardClass }" id="wpforms-template-generate">
|
||||
<div class="wpforms-template-thumbnail">
|
||||
<div class="wpforms-template-thumbnail-placeholder">
|
||||
<img src="${ strings.templateCard.imageSrc }" alt="${ strings.templateCard.name }" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div class="wpforms-template-name-wrap">
|
||||
<h3 class="wpforms-template-name categories has-access favorite slug subcategories fields" data-categories="all,new" data-subcategories="" data-fields="" data-has-access="1" data-favorite="" data-slug="generate">
|
||||
${ strings.templateCard.name }
|
||||
</h3>
|
||||
<span class="wpforms-badge wpforms-badge-sm wpforms-badge-inline wpforms-badge-purple wpforms-badge-rounded">${ strings.templateCard.new }</span>
|
||||
</div>
|
||||
<p class="wpforms-template-desc">
|
||||
${ strings.templateCard.desc }
|
||||
</p>
|
||||
<div class="wpforms-template-buttons">
|
||||
<a href="#" class="${ buttonClass } wpforms-btn wpforms-btn-md wpforms-btn-purple-dark" ${ buttonAttr }>
|
||||
${ strings.templateCard.buttonTextInit }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the form when the generated form opened.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
maybeSaveForm() {
|
||||
// Only in case the generated form was used, we have a chat session in the localized vars.
|
||||
if ( wpforms_ai_chat_element.forms?.chatHtml && ! wpf.getQueryString( 'newform' ) ) {
|
||||
WPFormsBuilder.formSave( false );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the session from URL.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
updateLocationUrl() {
|
||||
history.replaceState( {}, null, wpf.updateQueryString( 'session', null ) );
|
||||
},
|
||||
};
|
||||
|
||||
return app;
|
||||
}( document, window, jQuery ) );
|
||||
|
||||
// Initialize.
|
||||
WPFormsAIFormGenerator.init();
|
||||
Vendored
Executable
+23
@@ -0,0 +1,23 @@
|
||||
var WPFormsAIFormGenerator=window.WPFormsAIFormGenerator||((e,r,s)=>{let l=wpforms_ai_form_generator,o={state:{},main:null,preview:null,init(){r.wpforms_builder?.is_ai_disabled||o.isLoaded||(o.updateLocationUrl(),o.events(),o.isLoaded=!0)},events(){s(e).on("wpformsSetupPanelBeforeInitTemplatesList",o.addTemplateCard),s("#wpforms-builder").on("wpformsBuilderReady",o.maybeSaveForm).on("wpformsBuilderPanelLoaded",o.panelLoaded)},panelLoaded(e,t){"setup"===t&&Promise.all([import(l.modules.main),import(l.modules.preview),import(l.modules.modals)]).then(([e,t,a])=>{o.main=e.default(o,s),o.preview=t.default(o,s),o.modals=a.default(o,s),o.main.init()})},addTemplateCard(){s("#wpforms-template-generate").length||(s("#wpforms-setup-templates-list .list").prepend(o.renderTemplateCard()),wpf.initTooltips(s("#wpforms-template-generate .wpforms-template-buttons")))},renderTemplateCard(){var e="generate"===r.wpforms_builder?.template_slug?"selected":"";let t="",a=!Object.keys(l.addonsData).length||l.dismissed.installAddons?"wpforms-template-generate":"wpforms-template-generate-install-addons";return l.isPro||l.liteConnectAllowed||(a+=" wpforms-inactive wpforms-help-tooltip wpforms-prevent-default",t=`data-tooltip-position="top" title="${l.templateCard.liteConnectNotAllowed}"`),l.isPro||l.liteConnectEnabled||!l.liteConnectAllowed||(a+=" enable-lite-connect-modal wpforms-prevent-default"),`
|
||||
<div class="wpforms-template ${e}" id="wpforms-template-generate">
|
||||
<div class="wpforms-template-thumbnail">
|
||||
<div class="wpforms-template-thumbnail-placeholder">
|
||||
<img src="${l.templateCard.imageSrc}" alt="${l.templateCard.name}" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div class="wpforms-template-name-wrap">
|
||||
<h3 class="wpforms-template-name categories has-access favorite slug subcategories fields" data-categories="all,new" data-subcategories="" data-fields="" data-has-access="1" data-favorite="" data-slug="generate">
|
||||
${l.templateCard.name}
|
||||
</h3>
|
||||
<span class="wpforms-badge wpforms-badge-sm wpforms-badge-inline wpforms-badge-purple wpforms-badge-rounded">${l.templateCard.new}</span>
|
||||
</div>
|
||||
<p class="wpforms-template-desc">
|
||||
${l.templateCard.desc}
|
||||
</p>
|
||||
<div class="wpforms-template-buttons">
|
||||
<a href="#" class="${a} wpforms-btn wpforms-btn-md wpforms-btn-purple-dark" ${t}>
|
||||
${l.templateCard.buttonTextInit}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`},maybeSaveForm(){wpforms_ai_chat_element.forms?.chatHtml&&!wpf.getQueryString("newform")&&WPFormsBuilder.formSave(!1)},updateLocationUrl(){history.replaceState({},null,wpf.updateQueryString("session",null))}};return o})(document,window,jQuery);WPFormsAIFormGenerator.init();
|
||||
+563
@@ -0,0 +1,563 @@
|
||||
/* global wpf, wpforms_ai_form_generator, wpforms_ai_chat_element, WPFormsBuilder, wpforms_builder, WPFormsChallenge */
|
||||
|
||||
/**
|
||||
* @param strings.panel.backToTemplates
|
||||
* @param strings.panel.emptyStateDesc
|
||||
* @param strings.panel.emptyStateTitle
|
||||
* @param strings.templateCard.buttonTextContinue
|
||||
* @param wpforms_ai_chat_element.forms.responseHistory
|
||||
* @param wpforms_builder.template_slug
|
||||
*/
|
||||
|
||||
/**
|
||||
* The WPForms AI form generator app.
|
||||
*
|
||||
* Main module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} generator The AI form generator.
|
||||
* @param {Object} $ jQuery function.
|
||||
*
|
||||
* @return {Object} The main module object.
|
||||
*/
|
||||
export default function( generator, $ ) { // eslint-disable-line max-lines-per-function
|
||||
/**
|
||||
* Localized strings.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const strings = wpforms_ai_form_generator;
|
||||
|
||||
/**
|
||||
* The main module object.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
const main = {
|
||||
/**
|
||||
* DOM elements.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
el: {},
|
||||
|
||||
/**
|
||||
* Init generator.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
init() {
|
||||
main.initState();
|
||||
main.initElementsCache();
|
||||
main.initStateProxy();
|
||||
|
||||
// Magic, we just need to set the state property to `true` to add the panel to the DOM.
|
||||
generator.state.panelAdd = true;
|
||||
|
||||
generator.preview.init();
|
||||
generator.modals.init();
|
||||
main.events();
|
||||
},
|
||||
|
||||
/**
|
||||
* Init generator state.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
initState() {
|
||||
generator.state = {
|
||||
formId: $( '#wpforms-builder-form' ).data( 'id' ),
|
||||
panelAdd: false,
|
||||
panelOpen: false,
|
||||
chatStart: false,
|
||||
aiResponse: null,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Events.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
events() {
|
||||
// Setup panel events.
|
||||
main.el.$setupPanel
|
||||
.on( 'click', '.wpforms-template-generate', main.event.clickGenerateFormBtn )
|
||||
.on( 'click', '.wpforms-template-generate-install-addons', generator.modals.openAddonsModal );
|
||||
|
||||
// Generator panel events.
|
||||
main.el.$generatorPanel
|
||||
.on( 'click', '.wpforms-btn-back-to-templates', main.event.clickBackToTemplatesBtn )
|
||||
.on( 'click', '.wpforms-ai-chat-reload-link', main.event.reloadPage )
|
||||
.on( 'click', '.wpforms-ai-chat-use-form', main.event.useForm );
|
||||
|
||||
// The Form Builder events
|
||||
main.el.$builder
|
||||
.on( 'wpformsPanelSwitch', main.event.panelSwitch );
|
||||
|
||||
// AI chat events.
|
||||
main.el.$doc
|
||||
.on( 'wpformsBuilderReady', main.maybeOpenPanel )
|
||||
.on( 'wpformsAIChatBeforeAddAnswer', main.event.chatBeforeAddAnswer )
|
||||
.on( 'wpformsAIChatAddedAnswer', main.event.chatAddedAnswer )
|
||||
.on( 'wpformsAIChatAfterRefresh', main.event.chatAfterRefresh )
|
||||
.on( 'wpformsAIChatSetActiveAnswer', main.event.chatSetActiveAnswer );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init elements cache.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
initElementsCache() {
|
||||
// Cache DOM elements.
|
||||
main.el.$doc = $( document );
|
||||
main.el.$builder = $( '#wpforms-builder' );
|
||||
main.el.$builderToolbar = $( '#wpforms-builder .wpforms-toolbar' );
|
||||
main.el.$templatesList = $( '#wpforms-setup-templates-list .list' ); // The templates list container.
|
||||
main.el.$templateCard = $( '#wpforms-template-generate' ); // The generator template card.
|
||||
main.el.$generatorPanel = $( '#wpforms-panel-ai-form' ); // The generator panel.
|
||||
main.el.$setupPanel = $( '#wpforms-panel-setup' ); // The Setup panel.
|
||||
main.el.$panelsContainer = $( '.wpforms-panels' ); // All panels container.
|
||||
main.el.$allPanels = $( '.wpforms-panel' ); // All panels.
|
||||
main.el.$chat = main.el.$generatorPanel.find( 'wpforms-ai-chat .wpforms-ai-chat' ); // The chat container.
|
||||
},
|
||||
|
||||
/**
|
||||
* Init state proxy.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
initStateProxy() {
|
||||
generator.state = new Proxy( generator.state, {
|
||||
set( state, key, value ) {
|
||||
// Set the state property.
|
||||
state[ key ] = value;
|
||||
|
||||
if ( typeof main.setStateHandler[ key ] !== 'function' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Run the set state property handler.
|
||||
main.setStateHandler[ key ]( value );
|
||||
|
||||
// Debug log.
|
||||
wpf.debug( 'Form Generator state changed:', key, '=', value );
|
||||
|
||||
return true;
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
event: {
|
||||
/**
|
||||
* Click on the `Generate Form` button.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
clickGenerateFormBtn( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
if ( $( this ).hasClass( 'wpforms-prevent-default' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the Form Generator panel.
|
||||
generator.state.panelOpen = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Click on the `Back to Templates` button.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
clickBackToTemplatesBtn() {
|
||||
// Close the Form Generator panel.
|
||||
generator.state.panelOpen = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Before adding the answer to the chat.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
chatBeforeAddAnswer( e ) {
|
||||
// Store the AI response data in state.
|
||||
generator.state.aiResponse = e.originalEvent.detail?.response;
|
||||
generator.state.aiResponseHistory = generator.state.aiResponseHistory || {};
|
||||
generator.state.aiResponseHistory[ generator.state.aiResponse?.responseId ] = generator.state.aiResponse;
|
||||
},
|
||||
|
||||
/**
|
||||
* The answer added to the chat.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
chatAddedAnswer( e ) {
|
||||
const chat = e.originalEvent.detail?.chat || {};
|
||||
|
||||
// Set chatStart state.
|
||||
if ( chat?.sessionId && ! generator.state.chatStart ) {
|
||||
generator.state.chatStart = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the chat triggered.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
chatAfterRefresh() {
|
||||
generator.preview.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set active answer. Switch form preview to the active answer.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
chatSetActiveAnswer( e ) {
|
||||
generator.state.aiResponse = generator.state.aiResponseHistory[ e.originalEvent.detail?.responseId ];
|
||||
},
|
||||
|
||||
/**
|
||||
* Click on the "use this form" button.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
useForm( e ) {
|
||||
e?.preventDefault();
|
||||
|
||||
const $button = $( this );
|
||||
const formId = generator.state.formId;
|
||||
|
||||
if ( ! formId || wpforms_builder.template_slug === 'generate' ) {
|
||||
main.useFormAjax( $button );
|
||||
} else {
|
||||
generator.modals.openExistingFormModal( $button );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Click on the "reload" link.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
reloadPage( e ) {
|
||||
e?.preventDefault();
|
||||
window.location = window.location + '&ai-form';
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch the Form Builder panel.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
panelSwitch() {
|
||||
generator.state.panelOpen = false;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Set state property handlers.
|
||||
*
|
||||
* Each handler runs when the appropriate state property was set.
|
||||
* For example, when `panelAdd` state property was set, the `setStateHandler.panelAdd()` handler will run.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
setStateHandler: {
|
||||
/**
|
||||
* `panelAdd` state handler.
|
||||
*
|
||||
* When the value is `true`, the panel will be added to the DOM, otherwise removed.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} value The state value.
|
||||
*/
|
||||
panelAdd( value ) {
|
||||
// Remove the panel from DOM.
|
||||
if ( ! value ) {
|
||||
main.el.$generatorPanel?.remove();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The panel already added, no need to add again.
|
||||
if ( main.el.$generatorPanel?.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add panel to DOM.
|
||||
main.el.$panelsContainer.append( main.render.generatorPanel() );
|
||||
|
||||
// Cache elements.
|
||||
main.el.$generatorPanel = $( '#wpforms-panel-ai-form' );
|
||||
main.el.$chat = main.el.$generatorPanel.find( 'wpforms-ai-chat .wpforms-ai-chat' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Panel open state handler.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} value The state value.
|
||||
*/
|
||||
panelOpen( value ) {
|
||||
main.el.$generatorPanel.toggleClass( 'active', value );
|
||||
main.el.$templateCard.addClass( 'selected' );
|
||||
main.setToolbarState( value );
|
||||
|
||||
// Freeze/unfreeze the Challenge.
|
||||
window.WPFormsChallenge?.core.freezeChallenge( value, strings.misc.frozenChallengeTooltip );
|
||||
$( 'body' ).toggleClass( 'wpforms-ai-form-generator-active', value );
|
||||
|
||||
if (
|
||||
generator.state.aiResponseHistory ||
|
||||
! wpforms_ai_chat_element.forms.responseHistory
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the response history if it exists.
|
||||
generator.state.aiResponseHistory = wpforms_ai_chat_element.forms.responseHistory;
|
||||
|
||||
const $activeResponse = main.el.$chat.find( '.wpforms-chat-item-answer.active' );
|
||||
const activeResponseId = $activeResponse.data( 'response-id' );
|
||||
|
||||
generator.state.aiResponse = generator.state.aiResponseHistory[ activeResponseId ];
|
||||
|
||||
// Scroll to the active response.
|
||||
$activeResponse[ 0 ].scrollIntoView( { behavior: 'smooth', block: 'end' } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Chat start state handler.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} value The state value.
|
||||
*/
|
||||
chatStart( value ) {
|
||||
if ( ! value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the generator template card button text.
|
||||
main.el.$templateCard
|
||||
.addClass( 'selected' )
|
||||
.find( '.wpforms-template-generate' )
|
||||
.text( strings.templateCard.buttonTextContinue );
|
||||
},
|
||||
|
||||
/**
|
||||
* AI response state handler.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} response The response data.
|
||||
*/
|
||||
aiResponse( response ) {
|
||||
if ( ! response ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the preview.
|
||||
generator.preview.update();
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the form preview update in progress.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} value Flag value.
|
||||
*/
|
||||
isPreviewUpdate( value ) {
|
||||
main.el.$chat.toggleClass( 'wpforms-ai-chat-inactive', value );
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* HTML renderers.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
render: {
|
||||
/**
|
||||
* Render generator panel HTML.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return {string} The panel markup.
|
||||
*/
|
||||
generatorPanel() {
|
||||
return `
|
||||
<div class="wpforms-panel wpforms-panel-fields" id="wpforms-panel-ai-form">
|
||||
<div class="wpforms-panel-sidebar-content">
|
||||
<div class="wpforms-panel-sidebar">
|
||||
<div class="wpforms-panel-sidebar-header">
|
||||
<button type="button" class="wpforms-btn-back-to-templates" aria-label="${ strings.panel.backToTemplates }">
|
||||
${ strings.panel.backToTemplates }
|
||||
</button>
|
||||
</div>
|
||||
<wpforms-ai-chat mode="forms" class="wpforms-ai-chat-blue"/>
|
||||
</div>
|
||||
<div class="wpforms-panel-content-wrap">
|
||||
<div class="wpforms-panel-content">
|
||||
<div class="wpforms-panel-empty-state">
|
||||
<h4>${ strings.panel.emptyStateTitle }</h4>
|
||||
<p>${ strings.panel.emptyStateDesc }</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Maybe open the form generator panel.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
maybeOpenPanel() {
|
||||
// Open the panel only if the `ai-form` query string parameter exists.
|
||||
if ( ! window.location.search.includes( '&ai-form' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the query string parameter from the URL.
|
||||
history.replaceState( {}, null, wpf.updateQueryString( 'ai-form', null ) );
|
||||
|
||||
// Open the LiteConnect modal if it is not enabled.
|
||||
const $buttonLiteConnect = $( '.wpforms-template-generate.enable-lite-connect-modal' );
|
||||
|
||||
if ( $buttonLiteConnect.length ) {
|
||||
setTimeout(
|
||||
function() {
|
||||
$buttonLiteConnect.trigger( 'click' );
|
||||
},
|
||||
0
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the panel if all addons are installed OR the modal is dismissed.
|
||||
if ( ! Object.keys( strings.addonsData ).length || strings.dismissed.installAddons ) {
|
||||
generator.state.panelOpen = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the addons install modal.
|
||||
generator.modals.openAddonsModal( null );
|
||||
},
|
||||
|
||||
/**
|
||||
* The "Use this form" ajax call.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {jQuery} $button Button element.
|
||||
*/
|
||||
useFormAjax( $button ) {
|
||||
const sessionId = $button.closest( '.wpforms-ai-chat' ).data( 'session-id' );
|
||||
const responseId = $button.closest( '.wpforms-chat-item' ).data( 'response-id' );
|
||||
|
||||
WPFormsBuilder.showLoadingOverlay();
|
||||
|
||||
// Rate the response.
|
||||
main.getChatElement()?.wpformsAiApi.rate( true, responseId );
|
||||
|
||||
// Do not display the alert about unsaved changes.
|
||||
WPFormsBuilder.setCloseConfirmation( false );
|
||||
|
||||
const data = {
|
||||
action: 'wpforms_use_ai_form',
|
||||
nonce: strings.nonce,
|
||||
formId: generator.state.formId,
|
||||
formData: generator.state.aiResponseHistory[ responseId ],
|
||||
sessionId,
|
||||
chatHtml: $button.closest( 'wpforms-ai-chat' ).html(),
|
||||
responseHistory: generator.state.aiResponseHistory,
|
||||
};
|
||||
|
||||
generator.preview.closeTooltips();
|
||||
|
||||
$.post( strings.ajaxUrl, data )
|
||||
.done( function( res ) {
|
||||
if ( ! res.success ) {
|
||||
wpf.debug( 'Form Generator AJAX error:', res.data.error ?? res.data );
|
||||
return;
|
||||
}
|
||||
|
||||
const newForm = ! data.formId ? '&newform=1' : '';
|
||||
|
||||
if ( ! window.WPFormsChallenge ) {
|
||||
window.location.assign( res.data.redirect + newForm );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// When the Challenge is active, we need to resume it and continue the steps.
|
||||
WPFormsChallenge.core.resumeChallengeAndExec( {}, () => {
|
||||
WPFormsChallenge.core.stepCompleted( 2 )
|
||||
.done( () => {
|
||||
window.location.assign( res.data.redirect + newForm );
|
||||
} );
|
||||
} );
|
||||
} )
|
||||
.fail( function( xhr ) {
|
||||
wpf.debug( 'Form Generator AJAX error:', xhr.responseText ?? xhr.statusText );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the Builder's toolbar state.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} isEmpty The toolbar is empty.
|
||||
*/
|
||||
setToolbarState( isEmpty ) {
|
||||
main.el.$builderToolbar.toggleClass( 'empty', isEmpty );
|
||||
main.el.$builderToolbar.find( '.js-wpforms-help span' ).toggleClass( 'screen-reader-text', ! isEmpty );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the AI chat element.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @return {HTMLElement} The chat element.
|
||||
*/
|
||||
getChatElement() {
|
||||
return main.el.$chat.parent()[ 0 ];
|
||||
},
|
||||
};
|
||||
|
||||
return main;
|
||||
}
|
||||
Vendored
Executable
+22
@@ -0,0 +1,22 @@
|
||||
export default function(o,n){let r=wpforms_ai_form_generator,l={el:{},init(){l.initState(),l.initElementsCache(),l.initStateProxy(),o.state.panelAdd=!0,o.preview.init(),o.modals.init(),l.events()},initState(){o.state={formId:n("#wpforms-builder-form").data("id"),panelAdd:!1,panelOpen:!1,chatStart:!1,aiResponse:null}},events(){l.el.$setupPanel.on("click",".wpforms-template-generate",l.event.clickGenerateFormBtn).on("click",".wpforms-template-generate-install-addons",o.modals.openAddonsModal),l.el.$generatorPanel.on("click",".wpforms-btn-back-to-templates",l.event.clickBackToTemplatesBtn).on("click",".wpforms-ai-chat-reload-link",l.event.reloadPage).on("click",".wpforms-ai-chat-use-form",l.event.useForm),l.el.$builder.on("wpformsPanelSwitch",l.event.panelSwitch),l.el.$doc.on("wpformsBuilderReady",l.maybeOpenPanel).on("wpformsAIChatBeforeAddAnswer",l.event.chatBeforeAddAnswer).on("wpformsAIChatAddedAnswer",l.event.chatAddedAnswer).on("wpformsAIChatAfterRefresh",l.event.chatAfterRefresh).on("wpformsAIChatSetActiveAnswer",l.event.chatSetActiveAnswer)},initElementsCache(){l.el.$doc=n(document),l.el.$builder=n("#wpforms-builder"),l.el.$builderToolbar=n("#wpforms-builder .wpforms-toolbar"),l.el.$templatesList=n("#wpforms-setup-templates-list .list"),l.el.$templateCard=n("#wpforms-template-generate"),l.el.$generatorPanel=n("#wpforms-panel-ai-form"),l.el.$setupPanel=n("#wpforms-panel-setup"),l.el.$panelsContainer=n(".wpforms-panels"),l.el.$allPanels=n(".wpforms-panel"),l.el.$chat=l.el.$generatorPanel.find("wpforms-ai-chat .wpforms-ai-chat")},initStateProxy(){o.state=new Proxy(o.state,{set(e,t,a){return e[t]=a,"function"==typeof l.setStateHandler[t]&&(l.setStateHandler[t](a),wpf.debug("Form Generator state changed:",t,"=",a)),!0}})},event:{clickGenerateFormBtn(e){e.preventDefault(),n(this).hasClass("wpforms-prevent-default")||(o.state.panelOpen=!0)},clickBackToTemplatesBtn(){o.state.panelOpen=!1},chatBeforeAddAnswer(e){o.state.aiResponse=e.originalEvent.detail?.response,o.state.aiResponseHistory=o.state.aiResponseHistory||{},o.state.aiResponseHistory[o.state.aiResponse?.responseId]=o.state.aiResponse},chatAddedAnswer(e){(e.originalEvent.detail?.chat||{})?.sessionId&&!o.state.chatStart&&(o.state.chatStart=!0)},chatAfterRefresh(){o.preview.clear()},chatSetActiveAnswer(e){o.state.aiResponse=o.state.aiResponseHistory[e.originalEvent.detail?.responseId]},useForm(e){e?.preventDefault();e=n(this);o.state.formId&&"generate"!==wpforms_builder.template_slug?o.modals.openExistingFormModal(e):l.useFormAjax(e)},reloadPage(e){e?.preventDefault(),window.location=window.location+"&ai-form"},panelSwitch(){o.state.panelOpen=!1}},setStateHandler:{panelAdd(e){e?l.el.$generatorPanel?.length||(l.el.$panelsContainer.append(l.render.generatorPanel()),l.el.$generatorPanel=n("#wpforms-panel-ai-form"),l.el.$chat=l.el.$generatorPanel.find("wpforms-ai-chat .wpforms-ai-chat")):l.el.$generatorPanel?.remove()},panelOpen(e){var t;l.el.$generatorPanel.toggleClass("active",e),l.el.$templateCard.addClass("selected"),l.setToolbarState(e),window.WPFormsChallenge?.core.freezeChallenge(e,r.misc.frozenChallengeTooltip),n("body").toggleClass("wpforms-ai-form-generator-active",e),!o.state.aiResponseHistory&&wpforms_ai_chat_element.forms.responseHistory&&(o.state.aiResponseHistory=wpforms_ai_chat_element.forms.responseHistory,t=(e=l.el.$chat.find(".wpforms-chat-item-answer.active")).data("response-id"),o.state.aiResponse=o.state.aiResponseHistory[t],e[0].scrollIntoView({behavior:"smooth",block:"end"}))},chatStart(e){e&&l.el.$templateCard.addClass("selected").find(".wpforms-template-generate").text(r.templateCard.buttonTextContinue)},aiResponse(e){e&&o.preview.update()},isPreviewUpdate(e){l.el.$chat.toggleClass("wpforms-ai-chat-inactive",e)}},render:{generatorPanel(){return`
|
||||
<div class="wpforms-panel wpforms-panel-fields" id="wpforms-panel-ai-form">
|
||||
<div class="wpforms-panel-sidebar-content">
|
||||
<div class="wpforms-panel-sidebar">
|
||||
<div class="wpforms-panel-sidebar-header">
|
||||
<button type="button" class="wpforms-btn-back-to-templates" aria-label="${r.panel.backToTemplates}">
|
||||
${r.panel.backToTemplates}
|
||||
</button>
|
||||
</div>
|
||||
<wpforms-ai-chat mode="forms" class="wpforms-ai-chat-blue"/>
|
||||
</div>
|
||||
<div class="wpforms-panel-content-wrap">
|
||||
<div class="wpforms-panel-content">
|
||||
<div class="wpforms-panel-empty-state">
|
||||
<h4>${r.panel.emptyStateTitle}</h4>
|
||||
<p>${r.panel.emptyStateDesc}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}},maybeOpenPanel(){if(window.location.search.includes("&ai-form")){history.replaceState({},null,wpf.updateQueryString("ai-form",null));let e=n(".wpforms-template-generate.enable-lite-connect-modal");e.length?setTimeout(function(){e.trigger("click")},0):!Object.keys(r.addonsData).length||r.dismissed.installAddons?o.state.panelOpen=!0:o.modals.openAddonsModal(null)}},useFormAjax(e){var t=e.closest(".wpforms-ai-chat").data("session-id"),a=e.closest(".wpforms-chat-item").data("response-id");WPFormsBuilder.showLoadingOverlay(),l.getChatElement()?.wpformsAiApi.rate(!0,a),WPFormsBuilder.setCloseConfirmation(!1);let s={action:"wpforms_use_ai_form",nonce:r.nonce,formId:o.state.formId,formData:o.state.aiResponseHistory[a],sessionId:t,chatHtml:e.closest("wpforms-ai-chat").html(),responseHistory:o.state.aiResponseHistory};o.preview.closeTooltips(),n.post(r.ajaxUrl,s).done(function(t){if(t.success){let e=s.formId?"":"&newform=1";window.WPFormsChallenge?WPFormsChallenge.core.resumeChallengeAndExec({},()=>{WPFormsChallenge.core.stepCompleted(2).done(()=>{window.location.assign(t.data.redirect+e)})}):window.location.assign(t.data.redirect+e)}else wpf.debug("Form Generator AJAX error:",t.data.error??t.data)}).fail(function(e){wpf.debug("Form Generator AJAX error:",e.responseText??e.statusText)})},setToolbarState(e){l.el.$builderToolbar.toggleClass("empty",e),l.el.$builderToolbar.find(".js-wpforms-help span").toggleClass("screen-reader-text",!e)},getChatElement(){return l.el.$chat.parent()[0]}};return l}
|
||||
Executable
+389
@@ -0,0 +1,389 @@
|
||||
/* global wpforms_ai_form_generator, wpf, WPFormsBuilder, wpforms_builder */
|
||||
|
||||
/**
|
||||
* @param strings.addonsAction
|
||||
* @param strings.addonsData
|
||||
* @param strings.addons.installTitle
|
||||
* @param strings.addons.installContent
|
||||
* @param strings.addons.activateContent
|
||||
* @param strings.addons.installButton
|
||||
* @param strings.addons.installConfirmButton
|
||||
* @param strings.addons.activateConfirmButton
|
||||
* @param strings.addons.cancelButton
|
||||
* @param strings.addons.dontShow
|
||||
* @param strings.addons.dismissErrorTitle
|
||||
* @param strings.addons.dismissError
|
||||
* @param strings.addons.addonsInstalledTitle
|
||||
* @param strings.addons.addonsActivatedTitle
|
||||
* @param strings.addons.addonsInstalledContent
|
||||
* @param strings.addons.okay
|
||||
* @param strings.addons.addonsInstallErrorTitle
|
||||
* @param strings.addons.addonsActivateErrorTitle
|
||||
* @param strings.addons.addonsInstallError
|
||||
* @param strings.addons.addonsInstallErrorNetwork
|
||||
* @param strings.adminNonce
|
||||
* @param strings.misc.warningExistingForm
|
||||
* @param this.$$confirm
|
||||
* @param this.$$cancel
|
||||
*/
|
||||
|
||||
/**
|
||||
* The WPForms AI form generator app.
|
||||
*
|
||||
* Modal windows' module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} generator The AI form generator.
|
||||
* @param {Object} $ jQuery function.
|
||||
*
|
||||
* @return {Object} The preview module object.
|
||||
*/
|
||||
export default function( generator, $ ) { // eslint-disable-line max-lines-per-function
|
||||
/**
|
||||
* Localized strings.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const strings = wpforms_ai_form_generator;
|
||||
|
||||
/**
|
||||
* The preview module object.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
const modals = {
|
||||
/**
|
||||
* DOM elements.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
el: {},
|
||||
|
||||
/**
|
||||
* AJAX error debug string.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
ajaxError: 'Form Generator AJAX error:',
|
||||
|
||||
/**
|
||||
* Init generator.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
init() {
|
||||
modals.el.$doc = $( document );
|
||||
modals.el.$templateCard = $( '#wpforms-template-generate' );
|
||||
|
||||
modals.events();
|
||||
},
|
||||
|
||||
/**
|
||||
* Register events.
|
||||
*/
|
||||
events() {
|
||||
modals.el.$doc.on( 'change', '.wpforms-ai-forms-install-addons-modal-dismiss', modals.dismissAddonsModal );
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the addons modal.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
openAddonsModal( e ) { // eslint-disable-line max-lines-per-function
|
||||
e?.preventDefault();
|
||||
|
||||
const spinner = '<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>';
|
||||
const isInstall = strings.addonsAction === 'install';
|
||||
const content = isInstall ? strings.addons.installContent : strings.addons.activateContent;
|
||||
|
||||
const options = {
|
||||
title: strings.addons.installTitle,
|
||||
content,
|
||||
type: 'purple',
|
||||
icon: 'fa fa-info-circle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: isInstall ? strings.addons.installConfirmButton : strings.addons.activateConfirmButton,
|
||||
btnClass: 'btn-confirm',
|
||||
keys: [ 'enter' ],
|
||||
action() {
|
||||
const label = isInstall ? strings.addons.installing : strings.addons.activating;
|
||||
|
||||
this.$$confirm.prop( 'disabled', true ).html( spinner + label );
|
||||
this.$$cancel.prop( 'disabled', true );
|
||||
|
||||
modals.installAddonsAjax( this );
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
text: strings.addons.cancelButton,
|
||||
keys: [ 'esc' ],
|
||||
btnClass: 'btn-cancel',
|
||||
action() {
|
||||
modals.updateGenerateFormButton( false );
|
||||
|
||||
// Open the Form Generator panel.
|
||||
setTimeout( () => {
|
||||
generator.state.panelOpen = true;
|
||||
}, 250 );
|
||||
},
|
||||
},
|
||||
},
|
||||
onOpenBefore() {
|
||||
// Add the checkbox to the modal.
|
||||
const dontShowAgain = `
|
||||
<label class="jconfirm-checkbox">
|
||||
<input type="checkbox" class="jconfirm-checkbox-input wpforms-ai-forms-install-addons-modal-dismiss">
|
||||
${ strings.addons.dontShow }
|
||||
</label>
|
||||
`;
|
||||
|
||||
this.$body
|
||||
.addClass( 'wpforms-ai-forms-install-addons-modal' )
|
||||
.find( '.jconfirm-buttons' )
|
||||
.after( dontShowAgain );
|
||||
},
|
||||
};
|
||||
|
||||
$.confirm( options );
|
||||
},
|
||||
|
||||
/**
|
||||
* Install required addons AJAX.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} previousModal Previous modal instance.
|
||||
*/
|
||||
installAddonsAjax( previousModal ) { // eslint-disable-line max-lines-per-function
|
||||
let chain = null;
|
||||
let errorDisplayed = false;
|
||||
|
||||
const postDone = function( res ) {
|
||||
if ( ! res.success ) {
|
||||
wpf.debug( modals.ajaxError, res.data.error ?? res.data );
|
||||
}
|
||||
|
||||
if ( ! res.success && ! errorDisplayed ) {
|
||||
errorDisplayed = true;
|
||||
|
||||
modals.openErrorModal( {
|
||||
title: strings.addonsAction === 'install' ? strings.addons.addonsInstallErrorTitle : strings.addons.addonsActivateErrorTitle,
|
||||
content: strings.addons.addonsInstallError,
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
const postFail = function( xhr ) {
|
||||
if ( errorDisplayed ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const error = xhr.responseText || strings.addons.addonsInstallErrorNetwork;
|
||||
let content = strings.addons.addonsInstallError;
|
||||
|
||||
content += error && error !== 'error' ? '<br>' + error : '';
|
||||
|
||||
wpf.debug( modals.ajaxError, content );
|
||||
|
||||
modals.openErrorModal( {
|
||||
title: strings.addonsAction === 'install' ? strings.addons.addonsInstallErrorTitle : strings.addons.addonsActivateErrorTitle,
|
||||
content,
|
||||
} );
|
||||
|
||||
errorDisplayed = true;
|
||||
};
|
||||
|
||||
// Do not display the alert about unsaved changes.
|
||||
WPFormsBuilder.setCloseConfirmation( false );
|
||||
|
||||
// Loop through all addons and make a chained AJAX calls.
|
||||
for ( const slug in strings.addonsData ) {
|
||||
const url = strings.addonsData[ slug ]?.url;
|
||||
const data = {
|
||||
action: url ? 'wpforms_install_addon' : 'wpforms_activate_addon',
|
||||
nonce : strings.adminNonce,
|
||||
plugin: url ? url : strings.addonsData[ slug ]?.path,
|
||||
type : 'addon',
|
||||
};
|
||||
|
||||
if ( chain === null ) {
|
||||
chain = $.post( strings.ajaxUrl, data, postDone );
|
||||
} else {
|
||||
chain = chain.then( () => {
|
||||
return $.post( strings.ajaxUrl, data, postDone );
|
||||
} );
|
||||
}
|
||||
|
||||
chain.fail( postFail );
|
||||
}
|
||||
|
||||
// Open the Addons Installed modal after the last AJAX call.
|
||||
chain
|
||||
.then( () => {
|
||||
if ( ! errorDisplayed ) {
|
||||
modals.openAddonsInstalledModal();
|
||||
}
|
||||
} )
|
||||
.always( () => {
|
||||
previousModal.close();
|
||||
modals.updateGenerateFormButton( false );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss or de-dismiss element.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
dismissAddonsModal() {
|
||||
const $checkbox = $( this );
|
||||
const isChecked = $checkbox.prop( 'checked' );
|
||||
|
||||
const data = {
|
||||
action: 'wpforms_dismiss_ai_form',
|
||||
nonce: strings.nonce,
|
||||
element: 'install-addons-modal',
|
||||
dismiss: isChecked,
|
||||
};
|
||||
|
||||
modals.updateGenerateFormButton( ! isChecked );
|
||||
|
||||
$.post( strings.ajaxUrl, data )
|
||||
.done( function( res ) {
|
||||
if ( res.success ) {
|
||||
return;
|
||||
}
|
||||
|
||||
modals.openErrorModal( {
|
||||
title: strings.addons.dismissErrorTitle,
|
||||
content: strings.addons.dismissError,
|
||||
} );
|
||||
|
||||
wpf.debug( modals.ajaxError, res.data.error ?? res.data );
|
||||
} )
|
||||
.fail( function( xhr ) {
|
||||
modals.openErrorModal( {
|
||||
title: strings.addons.dismissErrorTitle,
|
||||
content: strings.addons.dismissError + '<br>' + strings.addons.addonsInstallErrorNetwork,
|
||||
} );
|
||||
|
||||
wpf.debug( modals.ajaxError, xhr.responseText ?? xhr.statusText );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the Generate Form button to enable/disable install addons modal window.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} shouldInstallAddons Should open install addons modal.
|
||||
*/
|
||||
updateGenerateFormButton( shouldInstallAddons ) {
|
||||
if ( shouldInstallAddons ) {
|
||||
$( '.wpforms-template-generate' )
|
||||
.removeClass( 'wpforms-template-generate' )
|
||||
.addClass( 'wpforms-template-generate-install-addons' );
|
||||
} else {
|
||||
$( '.wpforms-template-generate-install-addons' )
|
||||
.removeClass( 'wpforms-template-generate-install-addons' )
|
||||
.addClass( 'wpforms-template-generate' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the Addons Installed modal.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
openAddonsInstalledModal() {
|
||||
const options = {
|
||||
title: strings.addonsAction === 'install' ? strings.addons.addonsInstalledTitle : strings.addons.addonsActivatedTitle,
|
||||
content: strings.addons.addonsInstalledContent,
|
||||
icon: 'fa fa-check-circle',
|
||||
type: 'green',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: strings.addons.okay,
|
||||
btnClass: 'btn-confirm',
|
||||
keys: [ 'enter' ],
|
||||
action() {
|
||||
WPFormsBuilder.showLoadingOverlay();
|
||||
window.location = window.location + '&ai-form';
|
||||
},
|
||||
},
|
||||
},
|
||||
onOpenBefore() {
|
||||
this.$body
|
||||
.addClass( 'wpforms-ai-forms-addons-installed-modal' );
|
||||
},
|
||||
};
|
||||
|
||||
$.confirm( options );
|
||||
},
|
||||
|
||||
/**
|
||||
* Warning for the existing form.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {jQuery} $button The "Use This Form" button.
|
||||
*/
|
||||
openExistingFormModal( $button ) {
|
||||
$.confirm( {
|
||||
title: wpforms_builder.heads_up,
|
||||
content: strings.misc.warningExistingForm,
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
type: 'orange',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: wpforms_builder.ok,
|
||||
btnClass: 'btn-confirm',
|
||||
keys: [ 'enter' ],
|
||||
action() {
|
||||
generator.main.useFormAjax( $button );
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
text: wpforms_builder.cancel,
|
||||
},
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the error modal.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} args Arguments.
|
||||
*/
|
||||
openErrorModal( args ) {
|
||||
const options = {
|
||||
title: args.title ?? false,
|
||||
content: args.content ?? false,
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
type: 'red',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: strings.addons.okay,
|
||||
btnClass: 'btn-confirm',
|
||||
keys: [ 'enter' ],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$.confirm( options );
|
||||
},
|
||||
};
|
||||
|
||||
return modals;
|
||||
}
|
||||
Vendored
Executable
+6
@@ -0,0 +1,6 @@
|
||||
export default function(a,r){let l=wpforms_ai_form_generator,i={el:{},ajaxError:"Form Generator AJAX error:",init(){i.el.$doc=r(document),i.el.$templateCard=r("#wpforms-template-generate"),i.events()},events(){i.el.$doc.on("change",".wpforms-ai-forms-install-addons-modal-dismiss",i.dismissAddonsModal)},openAddonsModal(n){n?.preventDefault();let o="install"===l.addonsAction;n=o?l.addons.installContent:l.addons.activateContent,n={title:l.addons.installTitle,content:n,type:"purple",icon:"fa fa-info-circle",buttons:{confirm:{text:o?l.addons.installConfirmButton:l.addons.activateConfirmButton,btnClass:"btn-confirm",keys:["enter"],action(){var n=o?l.addons.installing:l.addons.activating;return this.$$confirm.prop("disabled",!0).html('<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>'+n),this.$$cancel.prop("disabled",!0),i.installAddonsAjax(this),!1}},cancel:{text:l.addons.cancelButton,keys:["esc"],btnClass:"btn-cancel",action(){i.updateGenerateFormButton(!1),setTimeout(()=>{a.state.panelOpen=!0},250)}}},onOpenBefore(){var n=`
|
||||
<label class="jconfirm-checkbox">
|
||||
<input type="checkbox" class="jconfirm-checkbox-input wpforms-ai-forms-install-addons-modal-dismiss">
|
||||
${l.addons.dontShow}
|
||||
</label>
|
||||
`;this.$body.addClass("wpforms-ai-forms-install-addons-modal").find(".jconfirm-buttons").after(n)}};r.confirm(n)},installAddonsAjax(n){function o(n){n.success||wpf.debug(i.ajaxError,n.data.error??n.data),n.success||t||(t=!0,i.openErrorModal({title:"install"===l.addonsAction?l.addons.addonsInstallErrorTitle:l.addons.addonsActivateErrorTitle,content:l.addons.addonsInstallError}))}let a=null,t=!1;function e(n){var o;t||(n=n.responseText||l.addons.addonsInstallErrorNetwork,o=l.addons.addonsInstallError,o+=n&&"error"!==n?"<br>"+n:"",wpf.debug(i.ajaxError,o),i.openErrorModal({title:"install"===l.addonsAction?l.addons.addonsInstallErrorTitle:l.addons.addonsActivateErrorTitle,content:o}),t=!0)}for(var s in WPFormsBuilder.setCloseConfirmation(!1),l.addonsData){var d=l.addonsData[s]?.url;let n={action:d?"wpforms_install_addon":"wpforms_activate_addon",nonce:l.adminNonce,plugin:d||l.addonsData[s]?.path,type:"addon"};(a=null===a?r.post(l.ajaxUrl,n,o):a.then(()=>r.post(l.ajaxUrl,n,o))).fail(e)}a.then(()=>{t||i.openAddonsInstalledModal()}).always(()=>{n.close(),i.updateGenerateFormButton(!1)})},dismissAddonsModal(){var n=r(this).prop("checked"),o={action:"wpforms_dismiss_ai_form",nonce:l.nonce,element:"install-addons-modal",dismiss:n};i.updateGenerateFormButton(!n),r.post(l.ajaxUrl,o).done(function(n){n.success||(i.openErrorModal({title:l.addons.dismissErrorTitle,content:l.addons.dismissError}),wpf.debug(i.ajaxError,n.data.error??n.data))}).fail(function(n){i.openErrorModal({title:l.addons.dismissErrorTitle,content:l.addons.dismissError+"<br>"+l.addons.addonsInstallErrorNetwork}),wpf.debug(i.ajaxError,n.responseText??n.statusText)})},updateGenerateFormButton(n){n?r(".wpforms-template-generate").removeClass("wpforms-template-generate").addClass("wpforms-template-generate-install-addons"):r(".wpforms-template-generate-install-addons").removeClass("wpforms-template-generate-install-addons").addClass("wpforms-template-generate")},openAddonsInstalledModal(){var n={title:"install"===l.addonsAction?l.addons.addonsInstalledTitle:l.addons.addonsActivatedTitle,content:l.addons.addonsInstalledContent,icon:"fa fa-check-circle",type:"green",buttons:{confirm:{text:l.addons.okay,btnClass:"btn-confirm",keys:["enter"],action(){WPFormsBuilder.showLoadingOverlay(),window.location=window.location+"&ai-form"}}},onOpenBefore(){this.$body.addClass("wpforms-ai-forms-addons-installed-modal")}};r.confirm(n)},openExistingFormModal(n){r.confirm({title:wpforms_builder.heads_up,content:l.misc.warningExistingForm,icon:"fa fa-exclamation-circle",type:"orange",buttons:{confirm:{text:wpforms_builder.ok,btnClass:"btn-confirm",keys:["enter"],action(){a.main.useFormAjax(n)}},cancel:{text:wpforms_builder.cancel}}})},openErrorModal(n){n={title:n.title??!1,content:n.content??!1,icon:"fa fa-exclamation-circle",type:"red",buttons:{confirm:{text:l.addons.okay,btnClass:"btn-confirm",keys:["enter"]}}};r.confirm(n)}};return i}
|
||||
Executable
+410
@@ -0,0 +1,410 @@
|
||||
/* global wpforms_ai_form_generator, wpf, wpforms_addons */
|
||||
|
||||
/**
|
||||
* @param strings.dismissed.previewNotice
|
||||
* @param strings.licenseType
|
||||
* @param strings.previewNotice.btnUpgrade
|
||||
* @param strings.previewNotice.msgUpgrade
|
||||
* @param wpforms_ai_form_generator.addonFields
|
||||
*/
|
||||
|
||||
/**
|
||||
* The WPForms AI form generator app.
|
||||
*
|
||||
* Form preview module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} generator The AI form generator.
|
||||
* @param {Object} $ jQuery function.
|
||||
*
|
||||
* @return {Object} The preview module object.
|
||||
*/
|
||||
export default function( generator, $ ) { // eslint-disable-line max-lines-per-function
|
||||
/**
|
||||
* Localized strings.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const strings = wpforms_ai_form_generator;
|
||||
|
||||
/**
|
||||
* The preview module object.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
const preview = {
|
||||
/**
|
||||
* DOM elements.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
el: {},
|
||||
|
||||
/**
|
||||
* Mouse coordinates.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
mouse: {},
|
||||
|
||||
/**
|
||||
* Init module.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
init() {
|
||||
preview.el.$contentWrap = generator.main.el.$generatorPanel.find( '.wpforms-panel-content-wrap' );
|
||||
preview.el.$content = preview.el.$contentWrap.find( '.wpforms-panel-content' );
|
||||
preview.el.$emptyState = preview.el.$content.find( '.wpforms-panel-empty-state' );
|
||||
|
||||
preview.events();
|
||||
},
|
||||
|
||||
/**
|
||||
* Preview events.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
events() {
|
||||
// Track mouse coordinates.
|
||||
$( document ).on( 'mousemove', ( e ) => {
|
||||
preview.mouse.x = e.pageX;
|
||||
preview.mouse.y = e.pageY;
|
||||
} );
|
||||
|
||||
preview.el.$contentWrap.on( 'scroll', preview.closeTooltips );
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the preview according to the response stored in the generator state.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
update() { // eslint-disable-line complexity
|
||||
/**
|
||||
* @param response.fieldsOrder.length
|
||||
* @param response.settings.submit_text
|
||||
*/
|
||||
const response = generator.state.aiResponse;
|
||||
|
||||
if ( ! response || ! response.fields ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the preview update flag.
|
||||
generator.state.isPreviewUpdate = true;
|
||||
|
||||
// Reset preview fields. Here we will store the field ids that where added to the preview.
|
||||
generator.state.previewFields = [];
|
||||
|
||||
// Remove existing fields and hide empty state.
|
||||
preview.clear( false );
|
||||
|
||||
// Display the form header.
|
||||
preview.displayHeader( response );
|
||||
|
||||
for ( const key in response.fieldsOrder ) {
|
||||
const fieldId = response.fieldsOrder[ key ];
|
||||
preview.field( response.fields[ fieldId ], key );
|
||||
}
|
||||
|
||||
// Add submit button.
|
||||
if ( response.fieldsOrder?.length ) {
|
||||
preview.displaySubmit( response.settings?.submit_text || strings.panel.submitButton );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the empty state if there are no fields.
|
||||
preview.el.$emptyState.removeClass( 'wpforms-hidden-strict' );
|
||||
|
||||
generator.state.isPreviewUpdate = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* A single field preview.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {Object} fieldSettings Field settings.
|
||||
* @param {number} key Field key.
|
||||
*/
|
||||
async field( fieldSettings, key ) {
|
||||
// Add a field placeholder to the preview.
|
||||
const html = `
|
||||
<div id="wpforms-generator-field-${ fieldSettings.id ?? '' }" class="wpforms-ai-form-generator-preview-field">
|
||||
<div class="placeholder"></div>
|
||||
<div class="wpforms-field wpforms-field-${ fieldSettings.type ?? '' }"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
preview.el.$content.append( html );
|
||||
|
||||
const data = {
|
||||
action: 'wpforms_get_ai_form_field_preview',
|
||||
nonce: strings.nonce,
|
||||
field: fieldSettings,
|
||||
};
|
||||
|
||||
// Delay the AJAX request to simulate one-by-one field loading.
|
||||
await preview.delay( 300 * key );
|
||||
|
||||
// Field preview AJAX request.
|
||||
$.post( strings.ajaxUrl, data )
|
||||
.done( function( res ) {
|
||||
if ( ! res.success ) {
|
||||
wpf.debug( 'Form Generator AJAX error:', res.data.error ?? res.data );
|
||||
return;
|
||||
}
|
||||
|
||||
preview.displayField( res.data ?? '', fieldSettings );
|
||||
} )
|
||||
.fail( function( xhr ) {
|
||||
wpf.debug( 'Form Generator AJAX error:', xhr.responseText ?? xhr.statusText );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Display the field in his placeholder.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {string} fieldHtml Field HTML.
|
||||
* @param {Object} fieldSettings Field settings.
|
||||
*/
|
||||
displayField( fieldHtml, fieldSettings ) {
|
||||
if ( ! fieldSettings.id && fieldSettings.id !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $fieldBlock = preview.el.$content.find( '#wpforms-generator-field-' + fieldSettings.id );
|
||||
const $field = $fieldBlock.find( '.wpforms-field' );
|
||||
const $placeholder = $fieldBlock.find( '.placeholder' );
|
||||
|
||||
$placeholder
|
||||
.addClass( 'fade-out' );
|
||||
|
||||
$field
|
||||
.html( fieldHtml ?? '' )
|
||||
.addClass( 'fade-in' )
|
||||
.toggleClass( 'wpforms-hidden', ! fieldHtml ) // Hide preview if the field is empty.
|
||||
.toggleClass( 'required', fieldSettings.required === '1' ) // Display the required field mark (asterisk) on the field label.
|
||||
.toggleClass( 'label_empty', ! fieldSettings.label ); // The field with an empty label.
|
||||
|
||||
preview.initTooltip( $field );
|
||||
preview.initPageBreak( $field, fieldSettings );
|
||||
|
||||
generator.state.previewFields.push( fieldSettings.id );
|
||||
|
||||
// Detect whether all the fields are loaded.
|
||||
if ( generator.state.previewFields.length !== Object.keys( generator.state.aiResponse?.fields ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
generator.state.isPreviewUpdate = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get addons used in AI response.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @return {string} Addons used in the response.
|
||||
*/
|
||||
getAddonsUsedInResponse() { // eslint-disable-line complexity
|
||||
const response = generator.state.aiResponse;
|
||||
|
||||
if ( ! response || ! response.fields ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const addons = [];
|
||||
|
||||
for ( const key in response.fields ) {
|
||||
const addon = wpforms_ai_form_generator.addonFields[ response.fields[ key ].type ];
|
||||
|
||||
if ( ! addon ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const addonName = wpforms_addons[ 'wpforms-' + addon ]?.title.replace( strings.addons.addon, '' ).trim();
|
||||
|
||||
if ( ! addonName || addons.includes( addonName ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addons.push( addonName );
|
||||
}
|
||||
|
||||
if ( ! addons.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let lastAddon = addons.pop();
|
||||
|
||||
lastAddon += ' ' + strings.addons.addon;
|
||||
|
||||
return addons.length ? addons.join( ', ' ) + ', ' + strings.addons.and + ' ' + lastAddon : lastAddon;
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the page breaks.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {jQuery} $field Field jQuery object.
|
||||
* @param {Object} fieldSettings Field settings.
|
||||
*/
|
||||
initPageBreak( $field, fieldSettings ) {
|
||||
if ( fieldSettings.type === 'pagebreak' && ! [ 'top', 'bottom' ].includes( fieldSettings.position ) ) {
|
||||
$field.addClass( 'wpforms-pagebreak-normal' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the preview tooltip.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {jQuery} $field Field jQuery object.
|
||||
*/
|
||||
initTooltip( $field ) {
|
||||
const width = 260;
|
||||
const args = {
|
||||
content: strings.panel.tooltipTitle + '<br>' + strings.panel.tooltipText,
|
||||
trigger: 'manual',
|
||||
interactive: true,
|
||||
animationDuration: 100,
|
||||
delay: 0,
|
||||
side: [ 'top' ],
|
||||
contentAsHTML: true,
|
||||
functionPosition: ( instance, helper, position ) => {
|
||||
// Set the tooltip position based on the mouse coordinates.
|
||||
position.coord.top = preview.mouse.y - 57;
|
||||
position.coord.left = preview.mouse.x - ( width / 2 );
|
||||
|
||||
return position;
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize.
|
||||
$field.tooltipster( args );
|
||||
preview.toggleTooltipOnClick( $field );
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the preview tooltip on click.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {jQuery} $field Field jQuery object.
|
||||
*/
|
||||
toggleTooltipOnClick( $field ) {
|
||||
$field.on( 'click', () => {
|
||||
// Close opened tooltips on other fields.
|
||||
preview.closeTooltips();
|
||||
|
||||
const status = $field.tooltipster( 'status' );
|
||||
|
||||
$field.tooltipster( status.state === 'closed' ? 'open' : 'close' );
|
||||
|
||||
if ( status.state !== 'closed' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instance = $field.tooltipster( 'instance' );
|
||||
|
||||
// Adjust tooltip styling.
|
||||
instance._$tooltip.css( {
|
||||
height: 'auto',
|
||||
} );
|
||||
|
||||
instance._$tooltip.find( '.tooltipster-arrow' ).css( {
|
||||
left: '50%',
|
||||
} );
|
||||
|
||||
// Close the tooltip after 5 seconds.
|
||||
setTimeout( function() {
|
||||
preview.closeTooltips();
|
||||
}, 5000 );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Close tooltips.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*/
|
||||
closeTooltips() {
|
||||
preview.el.$content.find( '.wpforms-field' ).each( function() {
|
||||
const $this = $( this );
|
||||
|
||||
if ( $this.hasClass( 'tooltipstered' ) && $this.parent().length ) {
|
||||
$this.tooltipster( 'close' );
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Display the form header.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param {Object} response Button text.
|
||||
*/
|
||||
displayHeader( response ) {
|
||||
const title = `<h2 class="wpforms-ai-form-generator-preview-title">${ response.form_title ?? '' }</h2>`;
|
||||
|
||||
// Add form title.
|
||||
preview.el.$content.prepend( title );
|
||||
},
|
||||
|
||||
/**
|
||||
* Display the `submit` button.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {string} label Button text.
|
||||
*/
|
||||
displaySubmit( label ) {
|
||||
preview.el.$content
|
||||
.append( `<button type="button" value="${ label }" class="wpforms-ai-form-generator-preview-submit">${ label }</button>` );
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the preview content.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {boolean} isEmptyState Whether to show the empty state or not.
|
||||
*/
|
||||
clear( isEmptyState = true ) {
|
||||
preview.el.$content.find( '.wpforms-ai-form-generator-preview-field' ).remove();
|
||||
preview.el.$content.find( '.wpforms-ai-form-generator-preview-placeholder' ).remove();
|
||||
preview.el.$content.find( '.wpforms-ai-form-generator-preview-title' ).remove();
|
||||
preview.el.$content.find( '.wpforms-ai-form-generator-preview-addons-notice' ).remove();
|
||||
preview.el.$content.find( '.wpforms-ai-form-generator-preview-submit' ).remove();
|
||||
preview.el.$emptyState.toggleClass( 'wpforms-hidden-strict', ! isEmptyState );
|
||||
},
|
||||
|
||||
/**
|
||||
* Delay promise.
|
||||
*
|
||||
* @since 1.9.2
|
||||
*
|
||||
* @param {number} time Time in milliseconds.
|
||||
*
|
||||
* @return {Promise} Promise.
|
||||
*/
|
||||
delay( time ) {
|
||||
return new Promise( ( res ) => {
|
||||
setTimeout( res, time );
|
||||
} );
|
||||
},
|
||||
};
|
||||
|
||||
return preview;
|
||||
}
|
||||
Vendored
Executable
+6
@@ -0,0 +1,6 @@
|
||||
export default function(s,i){let n=wpforms_ai_form_generator,r={el:{},mouse:{},init(){r.el.$contentWrap=s.main.el.$generatorPanel.find(".wpforms-panel-content-wrap"),r.el.$content=r.el.$contentWrap.find(".wpforms-panel-content"),r.el.$emptyState=r.el.$content.find(".wpforms-panel-empty-state"),r.events()},events(){i(document).on("mousemove",e=>{r.mouse.x=e.pageX,r.mouse.y=e.pageY}),r.el.$contentWrap.on("scroll",r.closeTooltips)},update(){var e=s.state.aiResponse;if(e&&e.fields){for(var t in s.state.isPreviewUpdate=!0,s.state.previewFields=[],r.clear(!1),r.displayHeader(e),e.fieldsOrder){var o=e.fieldsOrder[t];r.field(e.fields[o],t)}e.fieldsOrder?.length?r.displaySubmit(e.settings?.submit_text||n.panel.submitButton):(r.el.$emptyState.removeClass("wpforms-hidden-strict"),s.state.isPreviewUpdate=!1)}},async field(t,e){var o=`
|
||||
<div id="wpforms-generator-field-${t.id??""}" class="wpforms-ai-form-generator-preview-field">
|
||||
<div class="placeholder"></div>
|
||||
<div class="wpforms-field wpforms-field-${t.type??""}"></div>
|
||||
</div>
|
||||
`,o=(r.el.$content.append(o),{action:"wpforms_get_ai_form_field_preview",nonce:n.nonce,field:t});await r.delay(300*e),i.post(n.ajaxUrl,o).done(function(e){e.success?r.displayField(e.data??"",t):wpf.debug("Form Generator AJAX error:",e.data.error??e.data)}).fail(function(e){wpf.debug("Form Generator AJAX error:",e.responseText??e.statusText)})},displayField(e,t){var o,i;!t.id&&0!==t.id||(i=(o=r.el.$content.find("#wpforms-generator-field-"+t.id)).find(".wpforms-field"),o.find(".placeholder").addClass("fade-out"),i.html(e??"").addClass("fade-in").toggleClass("wpforms-hidden",!e).toggleClass("required","1"===t.required).toggleClass("label_empty",!t.label),r.initTooltip(i),r.initPageBreak(i,t),s.state.previewFields.push(t.id),s.state.previewFields.length!==Object.keys(s.state.aiResponse?.fields).length)||(s.state.isPreviewUpdate=!1)},getAddonsUsedInResponse(){var e=s.state.aiResponse;if(!e||!e.fields)return"";var t,o,i=[];for(t in e.fields){var r=wpforms_ai_form_generator.addonFields[e.fields[t].type];r&&(r=wpforms_addons["wpforms-"+r]?.title.replace(n.addons.addon,"").trim())&&!i.includes(r)&&i.push(r)}return i.length?(o=i.pop(),o+=" "+n.addons.addon,i.length?i.join(", ")+", "+n.addons.and+" "+o:o):""},initPageBreak(e,t){"pagebreak"!==t.type||["top","bottom"].includes(t.position)||e.addClass("wpforms-pagebreak-normal")},initTooltip(e){var t={content:n.panel.tooltipTitle+"<br>"+n.panel.tooltipText,trigger:"manual",interactive:!0,animationDuration:100,delay:0,side:["top"],contentAsHTML:!0,functionPosition:(e,t,o)=>(o.coord.top=r.mouse.y-57,o.coord.left=r.mouse.x-130,o)};e.tooltipster(t),r.toggleTooltipOnClick(e)},toggleTooltipOnClick(t){t.on("click",()=>{r.closeTooltips();var e=t.tooltipster("status");t.tooltipster("closed"===e.state?"open":"close"),"closed"===e.state&&((e=t.tooltipster("instance"))._$tooltip.css({height:"auto"}),e._$tooltip.find(".tooltipster-arrow").css({left:"50%"}),setTimeout(function(){r.closeTooltips()},5e3))})},closeTooltips(){r.el.$content.find(".wpforms-field").each(function(){var e=i(this);e.hasClass("tooltipstered")&&e.parent().length&&e.tooltipster("close")})},displayHeader(e){e=`<h2 class="wpforms-ai-form-generator-preview-title">${e.form_title??""}</h2>`;r.el.$content.prepend(e)},displaySubmit(e){r.el.$content.append(`<button type="button" value="${e}" class="wpforms-ai-form-generator-preview-submit">${e}</button>`)},clear(e=!0){r.el.$content.find(".wpforms-ai-form-generator-preview-field").remove(),r.el.$content.find(".wpforms-ai-form-generator-preview-placeholder").remove(),r.el.$content.find(".wpforms-ai-form-generator-preview-title").remove(),r.el.$content.find(".wpforms-ai-form-generator-preview-addons-notice").remove(),r.el.$content.find(".wpforms-ai-form-generator-preview-submit").remove(),r.el.$emptyState.toggleClass("wpforms-hidden-strict",!e)},delay(t){return new Promise(e=>{setTimeout(e,t)})}};return r}
|
||||
Reference in New Issue
Block a user