| 1 |
// --------------------------------------------------------------------------------------------------------------------- |
| 2 |
// == File /includes/page-form-builder/preview/_out/bfb-preview.js |
| 3 |
// == BFB Preview Client — sends current structure via AJAX and loads preview URL into iframe |
| 4 |
// --------------------------------------------------------------------------------------------------------------------- |
| 5 |
(function (w, d) { |
| 6 |
'use strict'; |
| 7 |
|
| 8 |
/** |
| 9 |
* Simple logger alias (optional). |
| 10 |
* |
| 11 |
* @type {{log:Function, error:Function}} |
| 12 |
*/ |
| 13 |
var dev = (w._wpbc && w._wpbc.dev) ? w._wpbc.dev : { |
| 14 |
log: function () {}, |
| 15 |
error: function () {} |
| 16 |
}; |
| 17 |
|
| 18 |
/** |
| 19 |
* Preview client class. |
| 20 |
* Binds to a preview panel root that contains button + iframe. |
| 21 |
*/ |
| 22 |
class wpbc_bfb_preview_client { |
| 23 |
|
| 24 |
/** |
| 25 |
* @param {HTMLElement} root_el Root element of the preview panel. |
| 26 |
*/ |
| 27 |
constructor(root_el) { |
| 28 |
this.root_el = root_el; |
| 29 |
this.iframe = root_el.querySelector( '[data-wpbc-bfb-preview-iframe="1"]' ); |
| 30 |
this.loader = root_el.querySelector( '[data-wpbc-bfb-preview-loader="1"]' ); // NEW |
| 31 |
this.button = null; // No local "Update Preview" button in panel anymore. |
| 32 |
|
| 33 |
this.nonce = root_el.getAttribute( 'data-preview-nonce' ) || ''; |
| 34 |
this.is_busy = false; |
| 35 |
|
| 36 |
if ( !this.iframe ) { |
| 37 |
dev.error( 'wpbc_bfb_preview_client', 'Missing iframe element' ); |
| 38 |
return; |
| 39 |
} |
| 40 |
|
| 41 |
this.bind_events(); |
| 42 |
} |
| 43 |
|
| 44 |
|
| 45 |
/** |
| 46 |
* Show/hide the overlay loader over the iframe. |
| 47 |
* |
| 48 |
* @param {boolean} is_visible |
| 49 |
*/ |
| 50 |
set_loader_visible(is_visible) { |
| 51 |
if ( !this.loader ) { |
| 52 |
return; |
| 53 |
} |
| 54 |
if ( is_visible ) { |
| 55 |
this.loader.classList.add( 'is-visible' ); |
| 56 |
} else { |
| 57 |
this.loader.classList.remove( 'is-visible' ); |
| 58 |
} |
| 59 |
} |
| 60 |
|
| 61 |
/** |
| 62 |
* Bind UI events (panel-local button, if present). |
| 63 |
*/ |
| 64 |
bind_events() { |
| 65 |
var self = this; |
| 66 |
|
| 67 |
if ( ! this.button ) { |
| 68 |
return; |
| 69 |
} |
| 70 |
|
| 71 |
this.button.addEventListener( 'click', function () { |
| 72 |
self.update_preview(); |
| 73 |
} ); |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Get current BFB structure from global builder. |
| 78 |
* |
| 79 |
* @returns {Object|null} |
| 80 |
*/ |
| 81 |
get_current_structure() { |
| 82 |
if ( ! w.wpbc_bfb || typeof w.wpbc_bfb.get_structure !== 'function' ) { |
| 83 |
dev.error( 'wpbc_bfb_preview_client', 'wpbc_bfb.get_structure() is not available' ); |
| 84 |
return null; |
| 85 |
} |
| 86 |
|
| 87 |
try { |
| 88 |
return w.wpbc_bfb.get_structure(); |
| 89 |
} catch (e) { |
| 90 |
dev.error( 'wpbc_bfb_preview_client.get_current_structure', e ); |
| 91 |
return null; |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* Parse combined length value like "100%", "320px", "12.5rem". |
| 97 |
* |
| 98 |
* @param {string} value |
| 99 |
* @return {{num:string, unit:string}} |
| 100 |
*/ |
| 101 |
parse_length_value(value) { |
| 102 |
var raw = String(value == null ? '' : value).trim(); |
| 103 |
var m = raw.match(/^\s*(-?\d+(?:\.\d+)?)\s*([a-z%]*)\s*$/i); |
| 104 |
if (!m) { |
| 105 |
return { num: raw, unit: '' }; |
| 106 |
} |
| 107 |
return { num: (m[1] || ''), unit: (m[2] || '') }; |
| 108 |
} |
| 109 |
|
| 110 |
/** |
| 111 |
* Build current form settings payload (same structure as real Save): |
| 112 |
* { |
| 113 |
* options: { ...source-of-truth... }, |
| 114 |
* css_vars: { ...compiled... } |
| 115 |
* } |
| 116 |
* |
| 117 |
* @param {string} form_name |
| 118 |
* @returns {{options:Object, css_vars:Object}} |
| 119 |
*/ |
| 120 |
get_current_form_settings(form_name) { |
| 121 |
|
| 122 |
var form_settings = { |
| 123 |
options : {}, |
| 124 |
css_vars: {} |
| 125 |
}; |
| 126 |
|
| 127 |
// --- same event contract as ajax/_out/bfb-ajax.js ---------------------------------------------- |
| 128 |
wpbc_bfb__dispatch_event_safe( 'wpbc:bfb:form_settings:collect', { |
| 129 |
settings : form_settings, |
| 130 |
form_name: form_name || 'standard' |
| 131 |
} ); |
| 132 |
|
| 133 |
// Strict: require correct shape. |
| 134 |
if ( !form_settings || typeof form_settings !== 'object' ) { |
| 135 |
form_settings = { options: {}, css_vars: {} }; |
| 136 |
} |
| 137 |
if ( !form_settings.options || typeof form_settings.options !== 'object' ) { |
| 138 |
form_settings.options = {}; |
| 139 |
} |
| 140 |
if ( !form_settings.css_vars || typeof form_settings.css_vars !== 'object' ) { |
| 141 |
form_settings.css_vars = {}; |
| 142 |
} |
| 143 |
|
| 144 |
return form_settings; |
| 145 |
} |
| 146 |
|
| 147 |
/** |
| 148 |
* Get the currently selected calendar skin URL from the Builder page. |
| 149 |
* |
| 150 |
* @returns {string} |
| 151 |
*/ |
| 152 |
get_selected_calendar_skin_url() { |
| 153 |
var select_el = d.querySelector( '.js-wpbc-bfb-calendar-skin' ); |
| 154 |
if ( select_el && select_el.options ) { |
| 155 |
var selected_option = select_el.options[ select_el.selectedIndex ]; |
| 156 |
if ( selected_option ) { |
| 157 |
var selected_url = selected_option.getAttribute( 'data-wpbc-calendar-skin-url' ); |
| 158 |
if ( selected_url ) { |
| 159 |
return String( selected_url ); |
| 160 |
} |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
var stylesheet = d.getElementById( 'wpbc-calendar-skin-css' ); |
| 165 |
if ( stylesheet && stylesheet.href ) { |
| 166 |
return String( stylesheet.href ); |
| 167 |
} |
| 168 |
|
| 169 |
return ''; |
| 170 |
} |
| 171 |
|
| 172 |
/** |
| 173 |
* Apply the unsaved Builder calendar skin inside the loaded Preview iframe. |
| 174 |
* |
| 175 |
* @param {string} skin_url |
| 176 |
* @param {Function} done |
| 177 |
* @param {number} tries |
| 178 |
*/ |
| 179 |
apply_calendar_skin_to_iframe(skin_url, done, tries) { |
| 180 |
var self = this; |
| 181 |
var attempt_num = Number( tries || 0 ); |
| 182 |
|
| 183 |
if ( ! skin_url || ! this.iframe || ! this.iframe.contentWindow ) { |
| 184 |
if ( typeof done === 'function' ) { |
| 185 |
done(); |
| 186 |
} |
| 187 |
return; |
| 188 |
} |
| 189 |
|
| 190 |
try { |
| 191 |
if ( typeof this.iframe.contentWindow.wpbc__calendar__change_skin === 'function' ) { |
| 192 |
this.iframe.contentWindow.wpbc__calendar__change_skin( skin_url ); |
| 193 |
if ( typeof done === 'function' ) { |
| 194 |
done(); |
| 195 |
} |
| 196 |
return; |
| 197 |
} |
| 198 |
} catch ( e ) { |
| 199 |
dev.error( 'wpbc_bfb_preview_client.apply_calendar_skin_to_iframe', e ); |
| 200 |
} |
| 201 |
|
| 202 |
if ( attempt_num >= 20 ) { |
| 203 |
if ( typeof done === 'function' ) { |
| 204 |
done(); |
| 205 |
} |
| 206 |
return; |
| 207 |
} |
| 208 |
|
| 209 |
w.setTimeout( function () { |
| 210 |
self.apply_calendar_skin_to_iframe( skin_url, done, attempt_num + 1 ); |
| 211 |
}, 100 ); |
| 212 |
} |
| 213 |
|
| 214 |
/** |
| 215 |
* Send snapshot to server and update iframe src with returned preview URL. |
| 216 |
* |
| 217 |
* @param {{source_button?:HTMLElement}} [opts] |
| 218 |
*/ |
| 219 |
update_preview( opts ) { |
| 220 |
if ( this.is_busy ) { |
| 221 |
return; |
| 222 |
} |
| 223 |
|
| 224 |
var structure = this.get_current_structure(); |
| 225 |
if ( ! structure ) { |
| 226 |
return; |
| 227 |
} |
| 228 |
|
| 229 |
this.set_loader_visible( true ); |
| 230 |
|
| 231 |
var options = opts || {}; |
| 232 |
var source_btn = options.source_button || null; |
| 233 |
|
| 234 |
var $source_btn = null; |
| 235 |
if ( source_btn && w.jQuery && typeof w.wpbc_bfb__button_busy_start === 'function' ) { |
| 236 |
$source_btn = w.jQuery( source_btn ); |
| 237 |
if ( $source_btn && $source_btn.length ) { |
| 238 |
w.wpbc_bfb__button_busy_start( $source_btn ); |
| 239 |
} |
| 240 |
} |
| 241 |
|
| 242 |
this.is_busy = true; |
| 243 |
this.set_button_busy( true ); // local "Update Preview" button in the panel |
| 244 |
|
| 245 |
var payload = new w.FormData(); |
| 246 |
var cfg = w.WPBC_BFB_Ajax || {}; |
| 247 |
|
| 248 |
// Build settings payload (same as real saving). |
| 249 |
var form_settings = this.get_current_form_settings( cfg.form_name || 'standard' ); |
| 250 |
|
| 251 |
payload.append( 'action', 'WPBC_AJX_BFB_SAVE_FORM_CONFIG' ); |
| 252 |
payload.append( 'nonce', ( cfg.nonce_save || this.nonce || '' ) ); |
| 253 |
|
| 254 |
// IMPORTANT: |
| 255 |
// - form_name is the logical form key (usually 'standard' or selected form) |
| 256 |
payload.append( 'form_name', ( cfg.form_name || 'standard' ) ); |
| 257 |
payload.append( 'status', 'preview' ); |
| 258 |
payload.append( 'return_preview_url', '1' ); |
| 259 |
|
| 260 |
payload.append( 'engine', ( cfg.engine || 'bfb' ) ); |
| 261 |
payload.append( 'engine_version', ( cfg.engine_version || '1.0' ) ); |
| 262 |
|
| 263 |
payload.append( 'structure', JSON.stringify( structure ) ); |
| 264 |
|
| 265 |
// Send real settings (options + compiled css_vars). |
| 266 |
payload.append( 'settings', JSON.stringify( form_settings ) ); |
| 267 |
|
| 268 |
// ---------------------------------------------------------------------------- |
| 269 |
// Choose where advanced_form + content_form are taken from (auto|builder|advanced) |
| 270 |
// ---------------------------------------------------------------------------- |
| 271 |
var preview_source = wpbc_bfb_preview__get_source( cfg, source_btn ); |
| 272 |
payload.append( 'preview_source', preview_source ); |
| 273 |
|
| 274 |
var adv = null; |
| 275 |
|
| 276 |
// 1) Try Advanced Mode text (if selected / auto+dirty) |
| 277 |
if ( preview_source === 'advanced' || preview_source === 'auto' ) { |
| 278 |
|
| 279 |
adv = wpbc_bfb_preview__read_advanced_mode_payload( d, w ); |
| 280 |
|
| 281 |
var can_use_advanced = |
| 282 |
(preview_source === 'advanced') || |
| 283 |
(preview_source === 'auto' && adv && adv.is_dirty); |
| 284 |
|
| 285 |
if ( can_use_advanced ) { |
| 286 |
|
| 287 |
// Forced "advanced" but empty -> fallback to builder export. |
| 288 |
if ( ! wpbc_bfb_preview__has_text( adv.advanced_form ) && ! wpbc_bfb_preview__has_text( adv.content_form ) ) { |
| 289 |
|
| 290 |
if ( typeof w.wpbc_admin_show_message === 'function' ) { |
| 291 |
w.wpbc_admin_show_message( |
| 292 |
'Advanced Mode is selected, but editors are empty. Using Builder export.', |
| 293 |
'warning', |
| 294 |
6000 |
| 295 |
); |
| 296 |
} |
| 297 |
|
| 298 |
} else { |
| 299 |
|
| 300 |
if ( wpbc_bfb_preview__has_text( adv.advanced_form ) ) { |
| 301 |
payload.append( 'advanced_form', adv.advanced_form ); |
| 302 |
} |
| 303 |
if ( wpbc_bfb_preview__has_text( adv.content_form ) ) { |
| 304 |
payload.append( 'content_form', adv.content_form ); |
| 305 |
} |
| 306 |
|
| 307 |
} |
| 308 |
} |
| 309 |
} |
| 310 |
|
| 311 |
// 2) If not taken from Advanced Mode -> export from Builder structure (current behavior) |
| 312 |
var already_has_adv = |
| 313 |
payload.has && (payload.has( 'advanced_form' ) || payload.has( 'content_form' )); // some browsers |
| 314 |
// support |
| 315 |
// FormData.has |
| 316 |
|
| 317 |
// Fallback for old browsers (no FormData.has) |
| 318 |
if ( typeof payload.has !== 'function' ) { |
| 319 |
already_has_adv = false; // just try exporter if needed |
| 320 |
} |
| 321 |
|
| 322 |
if ( ! already_has_adv ) { |
| 323 |
|
| 324 |
if ( w.WPBC_BFB_Exporter && typeof w.WPBC_BFB_Exporter.export_all === 'function' ) { |
| 325 |
try { |
| 326 |
|
| 327 |
var width_combined = form_settings.options.booking_form_layout_width || ''; |
| 328 |
var parsed_width = this.parse_length_value( width_combined ); |
| 329 |
var width_unit = parsed_width.unit ? parsed_width.unit : '%'; |
| 330 |
|
| 331 |
var export_options = { |
| 332 |
gapPercent : 3, |
| 333 |
form_slug : (cfg.form_name || 'standard'), |
| 334 |
form_width_value: parsed_width.num, |
| 335 |
form_width_unit : width_unit |
| 336 |
}; |
| 337 |
|
| 338 |
var export_result = w.WPBC_BFB_Exporter.export_all( structure || [], export_options ); |
| 339 |
|
| 340 |
if ( export_result ) { |
| 341 |
if ( export_result.advanced_form ) { |
| 342 |
payload.append( 'advanced_form', export_result.advanced_form ); |
| 343 |
} |
| 344 |
if ( export_result.fields_data ) { |
| 345 |
payload.append( 'content_form', export_result.fields_data ); |
| 346 |
} |
| 347 |
} |
| 348 |
|
| 349 |
} catch ( e ) { |
| 350 |
console.error( 'WPBC BFB: export_all error', e ); |
| 351 |
} |
| 352 |
} |
| 353 |
} |
| 354 |
|
| 355 |
|
| 356 |
// Use shared helper if available; fallback to ajaxurl. |
| 357 |
var ajax_url = ''; |
| 358 |
if ( typeof w.wpbc_bfb__get_ajax_url === 'function' ) { |
| 359 |
ajax_url = w.wpbc_bfb__get_ajax_url(); |
| 360 |
} else if ( typeof w.ajaxurl !== 'undefined' && w.ajaxurl ) { |
| 361 |
ajax_url = w.ajaxurl; |
| 362 |
} |
| 363 |
|
| 364 |
var self = this; |
| 365 |
var preview_calendar_skin_url = this.get_selected_calendar_skin_url(); |
| 366 |
|
| 367 |
function end_busy_now() { |
| 368 |
self._end_busy( $source_btn ); |
| 369 |
} |
| 370 |
|
| 371 |
if ( ! ajax_url ) { |
| 372 |
dev.error( 'wpbc_bfb_preview_client', 'ajax URL is not defined' ); |
| 373 |
end_busy_now(); |
| 374 |
return; |
| 375 |
} |
| 376 |
|
| 377 |
w.fetch( ajax_url, { |
| 378 |
method: 'POST', |
| 379 |
body: payload |
| 380 |
} ) |
| 381 |
.then( function (response) { |
| 382 |
return response.json(); |
| 383 |
} ) |
| 384 |
.then( function (data) { |
| 385 |
if ( ! data || ! data.success || ! data.data || ! data.data.preview_url ) { |
| 386 |
dev.error( 'wpbc_bfb_preview_client', 'Preview AJAX error', data ); |
| 387 |
end_busy_now(); |
| 388 |
return; |
| 389 |
} |
| 390 |
|
| 391 |
if ( ! self.iframe ) { |
| 392 |
dev.error( 'wpbc_bfb_preview_client', 'Missing iframe element' ); |
| 393 |
end_busy_now(); |
| 394 |
return; |
| 395 |
} |
| 396 |
|
| 397 |
// Wait until iframe has really loaded the preview URL. |
| 398 |
var on_load = function () { |
| 399 |
self.iframe.removeEventListener( 'load', on_load ); |
| 400 |
self.apply_calendar_skin_to_iframe( preview_calendar_skin_url, end_busy_now ); |
| 401 |
}; |
| 402 |
|
| 403 |
self.iframe.addEventListener( 'load', on_load ); |
| 404 |
self.iframe.setAttribute( 'src', data.data.preview_url ); |
| 405 |
} ) |
| 406 |
.catch( function (err) { |
| 407 |
dev.error( 'wpbc_bfb_preview_client', 'Preview AJAX failed', err ); |
| 408 |
end_busy_now(); |
| 409 |
} ); |
| 410 |
} |
| 411 |
|
| 412 |
/** |
| 413 |
* Internal helper: stop busy state on panel button + optional top toolbar button. |
| 414 |
* |
| 415 |
* @param {jQuery|null} $source_btn |
| 416 |
* @private |
| 417 |
*/ |
| 418 |
_end_busy($source_btn) { |
| 419 |
this.is_busy = false; |
| 420 |
this.set_button_busy( false ); |
| 421 |
this.set_loader_visible( false ); // NEW |
| 422 |
|
| 423 |
if ( $source_btn && typeof w.wpbc_bfb__button_busy_end === 'function' ) { |
| 424 |
w.wpbc_bfb__button_busy_end( $source_btn ); |
| 425 |
} |
| 426 |
} |
| 427 |
|
| 428 |
/** |
| 429 |
* Force-reset any busy states related to preview. |
| 430 |
* This is called when switching back to Builder mode (soft-cancel UX). |
| 431 |
*/ |
| 432 |
reset_busy_state() { |
| 433 |
this.is_busy = false; |
| 434 |
this.set_button_busy( false ); // panel button, if any |
| 435 |
this.set_loader_visible( false ); // NEW |
| 436 |
|
| 437 |
if ( w.jQuery && typeof w.wpbc_bfb__button_busy_end === 'function' ) { |
| 438 |
// Top toolbar buttons that may show "Loading preview..." |
| 439 |
var $btns = w.jQuery( |
| 440 |
'[data-wpbc-bfb-top-preview-btn="1"],' + |
| 441 |
'[data-wpbc-bfb-top-refresh-btn="1"]' |
| 442 |
); |
| 443 |
|
| 444 |
$btns.each( function () { |
| 445 |
var $b = w.jQuery( this ); |
| 446 |
if ( $b.hasClass( 'wpbc-is-busy' ) ) { |
| 447 |
w.wpbc_bfb__button_busy_end( $b ); |
| 448 |
} |
| 449 |
} ); |
| 450 |
} |
| 451 |
} |
| 452 |
|
| 453 |
/** |
| 454 |
* Simple busy indicator for the panel "Update Preview" button. |
| 455 |
* Reuses global WPBC busy helpers if available, so the spinner matches Save/Load buttons. |
| 456 |
* |
| 457 |
* @param {boolean} is_busy |
| 458 |
*/ |
| 459 |
set_button_busy( is_busy ) { |
| 460 |
if ( ! this.button ) { |
| 461 |
return; |
| 462 |
} |
| 463 |
|
| 464 |
// If jQuery + global helpers exist, use same spinner as other toolbar buttons. |
| 465 |
if ( w.jQuery && typeof w.wpbc_bfb__button_busy_start === 'function' && typeof w.wpbc_bfb__button_busy_end === 'function' ) { |
| 466 |
var $btn = w.jQuery( this.button ); |
| 467 |
|
| 468 |
if ( is_busy ) { |
| 469 |
w.wpbc_bfb__button_busy_start( $btn ); |
| 470 |
} else { |
| 471 |
w.wpbc_bfb__button_busy_end( $btn ); |
| 472 |
} |
| 473 |
|
| 474 |
return; |
| 475 |
} |
| 476 |
|
| 477 |
// Fallback: simple disabled state without spinner. |
| 478 |
if ( is_busy ) { |
| 479 |
this.button.setAttribute( 'disabled', 'disabled' ); |
| 480 |
this.button.classList.add( 'wpbc_bfb__preview_btn_busy' ); |
| 481 |
} else { |
| 482 |
this.button.removeAttribute( 'disabled' ); |
| 483 |
this.button.classList.remove( 'wpbc_bfb__preview_btn_busy' ); |
| 484 |
} |
| 485 |
} |
| 486 |
|
| 487 |
/** |
| 488 |
* Set current view mode: "builder" or "preview". |
| 489 |
* Applies/removes CSS class on <body>. |
| 490 |
* |
| 491 |
* @param {('builder'|'preview')} mode |
| 492 |
*/ |
| 493 |
set_mode( mode ) { |
| 494 |
var body = d.body; |
| 495 |
if ( ! body ) { |
| 496 |
return; |
| 497 |
} |
| 498 |
|
| 499 |
if ( mode === 'preview' ) { |
| 500 |
body.classList.add( 'wpbc_bfb__mode_preview' ); |
| 501 |
} else { |
| 502 |
body.classList.remove( 'wpbc_bfb__mode_preview' ); |
| 503 |
} |
| 504 |
} |
| 505 |
|
| 506 |
} |
| 507 |
|
| 508 |
// -- Helpers ------------------------------------------------------------------------------------------------------ |
| 509 |
/** |
| 510 |
* Is Preview mode currently active? |
| 511 |
* We prefer body class (set by client.set_mode), fallback to active tab id. |
| 512 |
* |
| 513 |
* @return {boolean} |
| 514 |
*/ |
| 515 |
function wpbc_bfb_preview__is_preview_mode_active() { |
| 516 |
var body = d.body; |
| 517 |
if ( body && body.classList && body.classList.contains( 'wpbc_bfb__mode_preview' ) ) { |
| 518 |
return true; |
| 519 |
} |
| 520 |
return (wpbc_bfb_preview__get_active_top_tab_id() === 'preview_tab'); |
| 521 |
} |
| 522 |
|
| 523 |
/** |
| 524 |
* When a form is loaded via AJAX while Preview tab is active, |
| 525 |
* refresh the iframe so it shows the newly loaded form. |
| 526 |
* |
| 527 |
* @param {wpbc_bfb_preview_client} client |
| 528 |
*/ |
| 529 |
function wpbc_bfb_preview__bind_form_ajax_loaded_events(client) { |
| 530 |
|
| 531 |
if ( ! client ) { |
| 532 |
return; |
| 533 |
} |
| 534 |
|
| 535 |
// Prevent double binding if this script is injected twice. |
| 536 |
if ( w.__wpbc_bfb_preview__form_ajax_loaded_bound === '1' ) { |
| 537 |
return; |
| 538 |
} |
| 539 |
w.__wpbc_bfb_preview__form_ajax_loaded_bound = '1'; |
| 540 |
|
| 541 |
var debounce_id = null; |
| 542 |
|
| 543 |
d.addEventListener( |
| 544 |
'wpbc:bfb:form:ajax_loaded', |
| 545 |
function (ev) { |
| 546 |
|
| 547 |
// Only do anything if Preview is currently active. |
| 548 |
if ( ! wpbc_bfb_preview__is_preview_mode_active() ) { |
| 549 |
return; |
| 550 |
} |
| 551 |
|
| 552 |
var det = (ev && ev.detail) ? ev.detail : {}; |
| 553 |
var fn = det.form_name ? String( det.form_name ) : ''; |
| 554 |
|
| 555 |
// Best-effort: keep cfg.form_name in sync for payload building. |
| 556 |
if ( fn ) { |
| 557 |
w.WPBC_BFB_Ajax = w.WPBC_BFB_Ajax || {}; |
| 558 |
w.WPBC_BFB_Ajax.form_name = fn; |
| 559 |
} |
| 560 |
|
| 561 |
// Debounce multiple rapid loads (or secondary events). |
| 562 |
if ( debounce_id ) { |
| 563 |
clearTimeout( debounce_id ); |
| 564 |
} |
| 565 |
|
| 566 |
debounce_id = setTimeout( function () { |
| 567 |
debounce_id = null; |
| 568 |
|
| 569 |
// Preview might have been left while waiting. |
| 570 |
if ( ! wpbc_bfb_preview__is_preview_mode_active() ) { |
| 571 |
return; |
| 572 |
} |
| 573 |
|
| 574 |
// If we are already sending preview, don't stack another request. |
| 575 |
if ( client.is_busy ) { |
| 576 |
// Try once more shortly (safe). |
| 577 |
setTimeout( function () { |
| 578 |
if ( wpbc_bfb_preview__is_preview_mode_active() && ! client.is_busy ) { |
| 579 |
client.update_preview( { source_button: null } ); |
| 580 |
} |
| 581 |
}, 250 ); |
| 582 |
return; |
| 583 |
} |
| 584 |
|
| 585 |
// Wait until builder API is present (form load can be async). |
| 586 |
var tries = 0; |
| 587 |
(function wait_for_builder() { |
| 588 |
|
| 589 |
if ( ! wpbc_bfb_preview__is_preview_mode_active() ) { |
| 590 |
return; |
| 591 |
} |
| 592 |
|
| 593 |
if ( w.wpbc_bfb && typeof w.wpbc_bfb.get_structure === 'function' ) { |
| 594 |
client.update_preview( { source_button: null } ); |
| 595 |
return; |
| 596 |
} |
| 597 |
|
| 598 |
tries++; |
| 599 |
if ( tries < 15 ) { |
| 600 |
setTimeout( wait_for_builder, 200 ); |
| 601 |
} |
| 602 |
})(); |
| 603 |
}, 180 ); |
| 604 |
|
| 605 |
}, |
| 606 |
{ passive: true } |
| 607 |
); |
| 608 |
} |
| 609 |
|
| 610 |
function wpbc_bfb_preview__get_source(cfg, btn) { |
| 611 |
|
| 612 |
// priority: button attr -> global cfg -> default |
| 613 |
var v = ''; |
| 614 |
try { |
| 615 |
if ( btn && btn.getAttribute ) { |
| 616 |
v = btn.getAttribute( 'data-wpbc-bfb-preview-source' ) || btn.getAttribute( 'data-wpbc-bfb-save-source' ) || ''; |
| 617 |
} |
| 618 |
} catch ( e ) {} |
| 619 |
|
| 620 |
if ( ! v && cfg && (cfg.preview_source || cfg.save_source) ) { |
| 621 |
v = cfg.preview_source || cfg.save_source; |
| 622 |
} |
| 623 |
|
| 624 |
v = String( v || 'auto' ).toLowerCase(); |
| 625 |
if ( [ 'builder', 'advanced', 'auto' ].indexOf( v ) === -1 ) { |
| 626 |
v = 'builder'; |
| 627 |
} |
| 628 |
return v; |
| 629 |
} |
| 630 |
|
| 631 |
function wpbc_bfb_preview__has_text(v) { |
| 632 |
return !! (v && String( v ).trim()); |
| 633 |
} |
| 634 |
|
| 635 |
function wpbc_bfb_preview__read_advanced_mode_payload(d, w) { |
| 636 |
|
| 637 |
// best: API (syncs CodeMirror -> textarea) |
| 638 |
if ( w.wpbc_bfb_advanced_editor_api && typeof w.wpbc_bfb_advanced_editor_api.get_values === 'function' ) { |
| 639 |
try { |
| 640 |
return w.wpbc_bfb_advanced_editor_api.get_values(); |
| 641 |
} catch ( e ) {} |
| 642 |
} |
| 643 |
|
| 644 |
// fallback: read textareas directly |
| 645 |
var ta_form = d.getElementById( 'wpbc_bfb__advanced_form_editor' ); |
| 646 |
var ta_content = d.getElementById( 'wpbc_bfb__content_form_editor' ); |
| 647 |
|
| 648 |
return { |
| 649 |
advanced_form: ta_form ? String( ta_form.value || '' ) : '', |
| 650 |
content_form : ta_content ? String( ta_content.value || '' ) : '', |
| 651 |
is_dirty : false |
| 652 |
}; |
| 653 |
} |
| 654 |
|
| 655 |
/** |
| 656 |
* Is this element the Preview TAB link in top tabs nav? |
| 657 |
* |
| 658 |
* @param {HTMLElement|null} el |
| 659 |
* @return {boolean} |
| 660 |
*/ |
| 661 |
function wpbc_bfb_preview__is_preview_tab_link(el) { |
| 662 |
if ( !el || !el.getAttribute ) { |
| 663 |
return false; |
| 664 |
} |
| 665 |
return ( |
| 666 |
el.getAttribute( 'data-wpbc-bfb-action' ) === 'panel' && |
| 667 |
el.getAttribute( 'data-wpbc-bfb-tab' ) === 'preview_tab' |
| 668 |
); |
| 669 |
} |
| 670 |
|
| 671 |
/** |
| 672 |
* Is this element the Builder TAB link in top tabs nav? |
| 673 |
* |
| 674 |
* @param {HTMLElement|null} el |
| 675 |
* @return {boolean} |
| 676 |
*/ |
| 677 |
function wpbc_bfb_preview__is_builder_tab_link(el) { |
| 678 |
if ( !el || !el.getAttribute ) { |
| 679 |
return false; |
| 680 |
} |
| 681 |
return ( |
| 682 |
el.getAttribute( 'data-wpbc-bfb-action' ) === 'panel' && |
| 683 |
el.getAttribute( 'data-wpbc-bfb-tab' ) === 'builder_tab' |
| 684 |
); |
| 685 |
} |
| 686 |
|
| 687 |
/** |
| 688 |
* Activate a "panel" tab and show its panel section (scoped). |
| 689 |
* |
| 690 |
* Strategy: |
| 691 |
* 1) Try to click the real top-tab link (best). |
| 692 |
* 2) If link does not exist (e.g. Preview tab removed from nav), do a scoped manual switch: |
| 693 |
* - use nav's data-wpbc-bfb-panels-root + data-wpbc-bfb-panel-class |
| 694 |
* - hide/show ONLY those outer panels |
| 695 |
* - DO NOT touch inner panels |
| 696 |
* |
| 697 |
* @param {string} tab_id |
| 698 |
* @return {boolean} |
| 699 |
*/ |
| 700 |
function wpbc_bfb__activate_panel_tab(tab_id) { |
| 701 |
|
| 702 |
tab_id = String( tab_id || '' ).trim(); |
| 703 |
if ( ! tab_id ) { |
| 704 |
return false; |
| 705 |
} |
| 706 |
|
| 707 |
// 1) Best: trigger existing top-tabs switching by clicking the tab link (if it exists). |
| 708 |
var tab_link = d.querySelector( '[data-wpbc-bfb-action="panel"][data-wpbc-bfb-tab="' + tab_id + '"]' ); |
| 709 |
if ( tab_link && typeof tab_link.click === 'function' ) { |
| 710 |
try { |
| 711 |
tab_link.click(); |
| 712 |
return true; |
| 713 |
} catch ( _e ) {} |
| 714 |
} |
| 715 |
|
| 716 |
// 2) Fallback: scoped manual toggle (outer panels only). |
| 717 |
var nav = d.getElementById( 'wpbc_bfb__top_horisontal_nav' ); |
| 718 |
|
| 719 |
// Determine outer panels root. |
| 720 |
var panels_root = d; |
| 721 |
var panels_root_selector = ''; |
| 722 |
|
| 723 |
if ( nav ) { |
| 724 |
panels_root_selector = nav.getAttribute( 'data-wpbc-bfb-panels-root' ) || ''; |
| 725 |
panels_root_selector = String( panels_root_selector || '' ).trim(); |
| 726 |
} |
| 727 |
|
| 728 |
// Fallback if attribute missing: prefer #wpbc_bfb__top_panels if present. |
| 729 |
if ( ! panels_root_selector ) { |
| 730 |
panels_root_selector = '#wpbc_bfb__top_panels'; |
| 731 |
} |
| 732 |
|
| 733 |
if ( panels_root_selector ) { |
| 734 |
try { |
| 735 |
var root_el = d.querySelector( panels_root_selector ); |
| 736 |
if ( root_el ) { |
| 737 |
panels_root = root_el; |
| 738 |
} |
| 739 |
} catch ( _e2 ) {} |
| 740 |
} |
| 741 |
|
| 742 |
// Determine outer base class (IMPORTANT: must be the OUTER base, not wpbc_bfb__tab_section). |
| 743 |
var panel_base_class = 'wpbc_bfb__top_tab_section'; |
| 744 |
if ( nav ) { |
| 745 |
var from_nav = nav.getAttribute( 'data-wpbc-bfb-panel-class' ) || ''; |
| 746 |
from_nav = String( from_nav || '' ).trim(); |
| 747 |
if ( from_nav ) { |
| 748 |
panel_base_class = from_nav; |
| 749 |
} |
| 750 |
} |
| 751 |
|
| 752 |
// Update active tab on nav (source of truth). |
| 753 |
if ( nav ) { |
| 754 |
nav.setAttribute( 'data-active-tab', tab_id ); |
| 755 |
} |
| 756 |
|
| 757 |
// Hide ONLY outer panels (scoped by base class). |
| 758 |
var selector_all = '.' + panel_base_class; |
| 759 |
var panel_nodes = panels_root.querySelectorAll( selector_all ); |
| 760 |
|
| 761 |
for ( var i = 0; i < panel_nodes.length; i++ ) { |
| 762 |
panel_nodes[i].style.display = 'none'; |
| 763 |
} |
| 764 |
|
| 765 |
// Show requested outer panel. |
| 766 |
var selector_active = '.' + panel_base_class + '__' + tab_id; |
| 767 |
var active_panel = panels_root.querySelector( selector_active ); |
| 768 |
|
| 769 |
if ( ! active_panel ) { |
| 770 |
// Backward-compatible fallback (if someone forgot to add outer classes). |
| 771 |
active_panel = panels_root.querySelector( '.wpbc_bfb__tab_section__' + tab_id ); |
| 772 |
} |
| 773 |
|
| 774 |
if ( active_panel ) { |
| 775 |
active_panel.style.display = ''; |
| 776 |
} |
| 777 |
|
| 778 |
// Mark active item in outer nav if it exists (Preview can be missing). |
| 779 |
if ( nav ) { |
| 780 |
var items = nav.querySelectorAll( '.wpbc_ui_el__horis_nav_item' ); |
| 781 |
for ( var j = 0; j < items.length; j++ ) { |
| 782 |
items[j].classList.remove( 'active' ); |
| 783 |
} |
| 784 |
|
| 785 |
var active_item = nav.querySelector( '.wpbc_ui_el__horis_nav_item__' + tab_id ); |
| 786 |
if ( active_item ) { |
| 787 |
active_item.classList.add( 'active' ); |
| 788 |
} |
| 789 |
} |
| 790 |
|
| 791 |
// Emit same event as bfb-top-tabs.js so other modules stay in sync. |
| 792 |
try { |
| 793 |
d.dispatchEvent( |
| 794 |
new CustomEvent( 'wpbc:bfb:top-tab', { |
| 795 |
detail: { |
| 796 |
tab : tab_id, |
| 797 |
nav_id: (nav && nav.id) ? String( nav.id ) : '' |
| 798 |
} |
| 799 |
} ) |
| 800 |
); |
| 801 |
} catch ( _e3 ) {} |
| 802 |
|
| 803 |
return true; |
| 804 |
} |
| 805 |
|
| 806 |
|
| 807 |
/** |
| 808 |
* Get currently active top tab id from the nav container. |
| 809 |
* |
| 810 |
* @return {string} |
| 811 |
*/ |
| 812 |
function wpbc_bfb_preview__get_active_top_tab_id() { |
| 813 |
var nav = d.getElementById( 'wpbc_bfb__top_horisontal_nav' ); |
| 814 |
if ( !nav ) { |
| 815 |
return ''; |
| 816 |
} |
| 817 |
return nav.getAttribute( 'data-active-tab' ) || ''; |
| 818 |
} |
| 819 |
|
| 820 |
/** |
| 821 |
* Sync preview "mode" with currently active top tab. |
| 822 |
* |
| 823 |
* Rules: |
| 824 |
* - preview_tab => enable preview mode (buttons make sense) |
| 825 |
* - any other tab => disable preview mode + reset busy states |
| 826 |
* |
| 827 |
* IMPORTANT: |
| 828 |
* - We do NOT change the active top tab here (no panel switching). |
| 829 |
* We only sync preview UI mode. |
| 830 |
* |
| 831 |
* @param {string} tab_id |
| 832 |
* @param {wpbc_bfb_preview_client} client |
| 833 |
*/ |
| 834 |
function wpbc_bfb_preview__sync_mode_to_tab(tab_id, client) { |
| 835 |
|
| 836 |
tab_id = String( tab_id || '' ); |
| 837 |
|
| 838 |
if ( !client ) { |
| 839 |
return; |
| 840 |
} |
| 841 |
|
| 842 |
if ( 'preview_tab' === tab_id ) { |
| 843 |
client.set_mode( 'preview' ); |
| 844 |
return; |
| 845 |
} |
| 846 |
|
| 847 |
// Leaving preview -> soft-cancel busy state and hide preview-specific toolbar. |
| 848 |
if ( typeof client.reset_busy_state === 'function' ) { |
| 849 |
client.reset_busy_state(); |
| 850 |
} |
| 851 |
client.set_mode( 'builder' ); |
| 852 |
} |
| 853 |
|
| 854 |
/** |
| 855 |
* Bind to top-tab switch event emitted by bfb-top-tabs.js. |
| 856 |
* |
| 857 |
* @param {wpbc_bfb_preview_client} client |
| 858 |
*/ |
| 859 |
function wpbc_bfb_preview__bind_top_tab_events(client) { |
| 860 |
|
| 861 |
// Sync immediately (in case event already fired before this script init). |
| 862 |
wpbc_bfb_preview__sync_mode_to_tab( wpbc_bfb_preview__get_active_top_tab_id(), client ); |
| 863 |
|
| 864 |
// React to future tab changes. |
| 865 |
d.addEventListener( 'wpbc:bfb:top-tab', function (ev) { |
| 866 |
var det = (ev && ev.detail) ? ev.detail : {}; |
| 867 |
var tab_id = det.tab ? String( det.tab ) : ''; |
| 868 |
var nav_id = det.nav_id ? String( det.nav_id ) : ''; |
| 869 |
var prev_id = det.prev_tab ? String( det.prev_tab ) : ''; |
| 870 |
|
| 871 |
// If user switched to preview_tab in the MAIN top nav, |
| 872 |
// remember what tab they came from. |
| 873 |
if ( nav_id === TOP_NAV_ID && tab_id === 'preview_tab' && prev_id && prev_id !== 'preview_tab' ) { |
| 874 |
return_tab_id = prev_id; |
| 875 |
} |
| 876 |
|
| 877 |
wpbc_bfb_preview__sync_mode_to_tab( tab_id, client ); |
| 878 |
} ); |
| 879 |
|
| 880 |
} |
| 881 |
|
| 882 |
|
| 883 |
// -- Bind | Init on Load ------------------------------------------------------------------------------------------ |
| 884 |
var TOP_NAV_ID = 'wpbc_bfb__top_horisontal_nav'; |
| 885 |
var return_tab_id = 'builder_tab'; |
| 886 |
|
| 887 |
function remember_return_tab_from_dom() { |
| 888 |
var cur = wpbc_bfb_preview__get_active_top_tab_id(); |
| 889 |
if (cur && cur !== 'preview_tab') { |
| 890 |
return_tab_id = cur; |
| 891 |
} |
| 892 |
} |
| 893 |
|
| 894 |
function get_return_tab_id() { |
| 895 |
var t = String(return_tab_id || '').trim(); |
| 896 |
if (!t || t === 'preview_tab') t = 'builder_tab'; |
| 897 |
return t; |
| 898 |
} |
| 899 |
|
| 900 |
/** |
| 901 |
* Auto-init preview client on Builder page and wire top toolbar buttons. |
| 902 |
*/ |
| 903 |
function wpbc_bfb_init_preview_client() { |
| 904 |
var root = d.querySelector( '[data-wpbc-bfb-preview-root="1"]' ); |
| 905 |
|
| 906 |
if ( !root ) { |
| 907 |
return; |
| 908 |
} |
| 909 |
|
| 910 |
var client = new wpbc_bfb_preview_client( root ); |
| 911 |
|
| 912 |
if ( !w.WPBC_BFB_Preview ) { |
| 913 |
w.WPBC_BFB_Preview = { |
| 914 |
client: client, |
| 915 |
|
| 916 |
show_preview: function (opts) { |
| 917 |
var opt = opts || {}; |
| 918 |
var src = opt.source_button || null; |
| 919 |
|
| 920 |
// Remember where we were BEFORE switching to preview. |
| 921 |
remember_return_tab_from_dom(); |
| 922 |
|
| 923 |
if ( ! wpbc_bfb_preview__is_preview_tab_link( src ) ) { |
| 924 |
wpbc_bfb__activate_panel_tab( 'preview_tab' ); |
| 925 |
} |
| 926 |
|
| 927 |
// Enable preview mode UI (shows refresh/back buttons etc). |
| 928 |
client.set_mode( 'preview' ); |
| 929 |
|
| 930 |
// IMPORTANT: Always regenerate when show_preview() is called (Preview tab click included). |
| 931 |
client.update_preview( { source_button: src } ); |
| 932 |
}, |
| 933 |
|
| 934 |
|
| 935 |
show_builder: function (opts) { |
| 936 |
var opt = opts || {}; |
| 937 |
var src = opt.source_button || null; |
| 938 |
|
| 939 |
if ( client && typeof client.reset_busy_state === 'function' ) { |
| 940 |
client.reset_busy_state(); |
| 941 |
} |
| 942 |
|
| 943 |
var back_to = get_return_tab_id(); |
| 944 |
|
| 945 |
// If Back button itself is not a real tab link -> just activate remembered tab. |
| 946 |
// If activation fails (tab removed), fallback to builder_tab. |
| 947 |
if ( ! wpbc_bfb_preview__is_builder_tab_link( src ) ) { |
| 948 |
if ( ! wpbc_bfb__activate_panel_tab( back_to ) ) { |
| 949 |
wpbc_bfb__activate_panel_tab( 'builder_tab' ); |
| 950 |
} |
| 951 |
} |
| 952 |
|
| 953 |
client.set_mode( 'builder' ); |
| 954 |
}, |
| 955 |
|
| 956 |
show_advanced_tab: function (opts) { |
| 957 |
var opt = opts || {}; |
| 958 |
|
| 959 |
if ( client && typeof client.reset_busy_state === 'function' ) { |
| 960 |
client.reset_busy_state(); |
| 961 |
} |
| 962 |
wpbc_bfb__activate_panel_tab( 'advanced_tab' ); |
| 963 |
client.set_mode( 'builder' ); |
| 964 |
} |
| 965 |
|
| 966 |
|
| 967 |
}; |
| 968 |
} |
| 969 |
|
| 970 |
// Listen for top-tab changes and sync preview mode automatically. |
| 971 |
wpbc_bfb_preview__bind_top_tab_events( client ); |
| 972 |
|
| 973 |
// When a form is loaded via AJAX, refresh preview (only if preview mode is active). |
| 974 |
wpbc_bfb_preview__bind_form_ajax_loaded_events( client ); |
| 975 |
|
| 976 |
wpbc_bfb_bind_top_toolbar_buttons( client ); |
| 977 |
} |
| 978 |
|
| 979 |
|
| 980 |
/** |
| 981 |
* Bind top toolbar Builder / Preview / Refresh buttons. |
| 982 |
* Supports multiple elements for each action (toolbar buttons, top tabs, etc.) |
| 983 |
* via data-wpbc-bfb-top-*-btn="1". |
| 984 |
*/ |
| 985 |
function wpbc_bfb_bind_top_toolbar_buttons(client) { |
| 986 |
|
| 987 |
var btn_preview_list = d.querySelectorAll( '[data-wpbc-bfb-top-preview-btn="1"]' ); |
| 988 |
var btn_builder_list = d.querySelectorAll( '[data-wpbc-bfb-top-builder-btn="1"]' ); |
| 989 |
var btn_refresh_list = d.querySelectorAll( '[data-wpbc-bfb-top-refresh-btn="1"]' ); |
| 990 |
|
| 991 |
function for_each_node(list, cb) { |
| 992 |
if ( !list || !list.length ) { |
| 993 |
return; |
| 994 |
} |
| 995 |
Array.prototype.forEach.call( list, function (el) { |
| 996 |
if ( el && typeof cb === 'function' ) { |
| 997 |
cb( el ); |
| 998 |
} |
| 999 |
} ); |
| 1000 |
} |
| 1001 |
|
| 1002 |
for_each_node( btn_preview_list, function (btn_preview) { |
| 1003 |
|
| 1004 |
// Prevent double binding if this script runs twice. |
| 1005 |
if ( btn_preview.getAttribute( 'data-wpbc-bfb-bound' ) === '1' ) { |
| 1006 |
return; |
| 1007 |
} |
| 1008 |
btn_preview.setAttribute( 'data-wpbc-bfb-bound', '1' ); |
| 1009 |
|
| 1010 |
btn_preview.addEventListener( 'click', function (e) { |
| 1011 |
e.preventDefault(); |
| 1012 |
|
| 1013 |
if ( w.WPBC_BFB_Preview && typeof w.WPBC_BFB_Preview.show_preview === 'function' ) { |
| 1014 |
w.WPBC_BFB_Preview.show_preview( { |
| 1015 |
source_button: btn_preview |
| 1016 |
} ); |
| 1017 |
} |
| 1018 |
} ); |
| 1019 |
} ); |
| 1020 |
|
| 1021 |
for_each_node( btn_refresh_list, function (btn_refresh) { |
| 1022 |
|
| 1023 |
if ( btn_refresh.getAttribute( 'data-wpbc-bfb-bound' ) === '1' ) { |
| 1024 |
return; |
| 1025 |
} |
| 1026 |
btn_refresh.setAttribute( 'data-wpbc-bfb-bound', '1' ); |
| 1027 |
|
| 1028 |
btn_refresh.addEventListener( 'click', function (e) { |
| 1029 |
e.preventDefault(); |
| 1030 |
|
| 1031 |
if ( w.WPBC_BFB_Preview && typeof w.WPBC_BFB_Preview.show_preview === 'function' ) { |
| 1032 |
w.WPBC_BFB_Preview.show_preview( { |
| 1033 |
update : true, |
| 1034 |
source_button: btn_refresh |
| 1035 |
} ); |
| 1036 |
} |
| 1037 |
} ); |
| 1038 |
} ); |
| 1039 |
|
| 1040 |
for_each_node( btn_builder_list, function (btn_builder) { |
| 1041 |
|
| 1042 |
if ( btn_builder.getAttribute( 'data-wpbc-bfb-bound' ) === '1' ) { |
| 1043 |
return; |
| 1044 |
} |
| 1045 |
btn_builder.setAttribute( 'data-wpbc-bfb-bound', '1' ); |
| 1046 |
|
| 1047 |
btn_builder.addEventListener( 'click', function (e) { |
| 1048 |
e.preventDefault(); |
| 1049 |
|
| 1050 |
if ( w.WPBC_BFB_Preview && typeof w.WPBC_BFB_Preview.show_builder === 'function' ) { |
| 1051 |
w.WPBC_BFB_Preview.show_builder( { source_button: btn_builder } ); |
| 1052 |
} |
| 1053 |
} ); |
| 1054 |
} ); |
| 1055 |
} |
| 1056 |
|
| 1057 |
|
| 1058 |
if ( d.readyState === 'complete' || d.readyState === 'interactive' ) { |
| 1059 |
setTimeout( wpbc_bfb_init_preview_client, 0 ); |
| 1060 |
} else { |
| 1061 |
d.addEventListener( 'DOMContentLoaded', wpbc_bfb_init_preview_client ); |
| 1062 |
} |
| 1063 |
|
| 1064 |
})( window, document ); |
| 1065 |
|