/*! elementor-pro - v3.33.0 - 11-11-2025 */ .elementor-widget-woocommerce-purchase-summary{color:#69727d;font-family:Roboto,sans-serif;font-size:14px}.elementor-widget-woocommerce-purchase-summary table tbody tr:hover>td,.elementor-widget-woocommerce-purchase-summary table tbody tr:hover>th{background-color:initial}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-details{display:flex;flex-wrap:wrap;margin:0 0 2em;padding-inline-start:0}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-details li{border-inline-end-color:var(--payment-details-border-color,#d5d8dc);border-inline-end-style:var(--payment-details-border-type,solid);border-inline-end-width:var(--payment-details-border-width,1px);color:var(--payment-details-titles-color,#000);float:unset;font-size:14px;font-weight:700;margin-inline-end:var(--payment-details-space-between,4em);margin-bottom:30px;padding-inline-end:var(--payment-details-space-between,4em);text-transform:capitalize}@media (max-width:1024px){.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-details li{border-bottom:var(--payment-details-border-width,1px) var(--payment-details-border-type,solid) var(--payment-details-border-color,#d5d8dc);border-inline-end:none;display:flex;justify-content:space-between;margin-inline-end:0;margin-bottom:0;padding-inline-end:0;padding-bottom:calc(var(--payment-details-space-between, 20px) / 2);padding-top:calc(var(--payment-details-space-between, 20px) / 2);width:100%}}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-details li strong{color:var(--payment-details-items-color,#69727d);font-size:14px;font-style:normal;font-weight:400;letter-spacing:0;line-height:normal;margin-top:var(--payment-details-titles-spacing,10px);text-shadow:none;text-transform:none}@media (max-width:1024px){.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-details li strong{margin-top:0}}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-bacs-bank-details .wc-bacs-bank-details{display:flex;flex-wrap:wrap;padding-inline-start:0}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-bacs-bank-details .wc-bacs-bank-details li{border-inline-end-color:var(--bank-details-border-color,#d5d8dc);border-inline-end-style:var(--bank-details-border-type,solid);border-inline-end-width:var(--bank-details-border-width,1px);color:var(--bank-details-titles-color,#000);float:unset;font-size:14px;font-weight:700;margin-inline-end:var(--bank-details-space-between,4em);margin-bottom:30px;padding-inline-end:var(--bank-details-space-between,4em);text-transform:capitalize}@media (max-width:1024px){.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-bacs-bank-details .wc-bacs-bank-details li{border-bottom:var(--bank-details-border-width,1px) var(--bank-details-border-type,solid) var(--bank-details-border-color,#d5d8dc);border-inline-end:none;display:flex;justify-content:space-between;margin-inline-end:0;margin-bottom:0;padding-inline-end:0;padding-bottom:calc(var(--bank-details-space-between, 20px) / 2);padding-top:calc(var(--bank-details-space-between, 20px) / 2);width:100%}}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-bacs-bank-details .wc-bacs-bank-details li strong{color:var(--bank-details-items-color,#69727d);font-size:14px;font-style:normal;font-weight:400;letter-spacing:0;line-height:normal;margin-top:var(--bank-details-titles-spacing,10px);text-shadow:none;text-transform:none}@media (max-width:1024px){.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-bacs-bank-details .wc-bacs-bank-details li strong{margin-top:0}}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-bacs-bank-details .wc-bacs-bank-details li:last-of-type{border-inline-end:none}.elementor-widget-woocommerce-purchase-summary .woocommerce .wc-item-meta .wc-item-meta-label,.elementor-widget-woocommerce-purchase-summary .woocommerce .wc-item-meta li p{color:var(--order-details-variations-color,#69727d)}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table{background-color:var(--sections-background-color,#fff);border-color:var(--sections-border-color,#d5d8dc);border-radius:var(--sections-border-radius,3px);border-style:var(--sections-border-type,solid);border-width:1px;font-size:14px;margin-bottom:0;padding:var(--sections-padding,15px 30px)}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table .button.alt{background-color:transparent;border-color:var(--buttons-border-color,#5bc0de);border-radius:var(--button-border-radius,3px);border-style:var(--buttons-border-type,solid);border-width:2px;color:var(--button-normal-text-color,#69727d);padding:var(--button-padding,5px 10px);vertical-align:middle}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table .button.alt:hover{color:var(--button-hover-text-color,#69727d);transition-duration:var(--button-hover-transition-duration,.3s)}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table td,.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table th{border:0;border-top:var(--tables-divider-border-width,1px) var(--tables-divider-border-type,solid) var(--tables-divider-border-color,#d5d8dc);padding-block:calc(var(--order-details-rows-gap, 18px) / 2);padding-inline:0}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table thead tr th{border-top:none;color:var(--order-details-titles-totals-color,#000);padding-top:0}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table thead tr th span{color:var(--order-details-titles-totals-color,#000)}@media (min-width:1025px){.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table tbody td{line-height:unset;vertical-align:top}}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table tbody td .woocommerce-Price-amount{color:var(--order-details-items-color,#69727d)}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table tfoot td,.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table tfoot th{color:var(--order-details-titles-totals-color,#000)}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table tfoot tr:last-child td,.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table tfoot tr:last-child th{padding-bottom:0}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table .product-quantity,.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table td.download-expires,.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table td.download-remaining{color:var(--order-details-items-color,#69727d);font-weight:400}.elementor-widget-woocommerce-purchase-summary .woocommerce .shop_table .product-purchase-note td{border-top:none;color:var(--general-text-color,#69727d);padding-top:0}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-table--order-downloads tr td:before{color:var(--order-details-titles-totals-color,#000)}.elementor-widget-woocommerce-purchase-summary .woocommerce .order-again .button{background:transparent;border:2px solid #5bc0de;border-color:var(--buttons-border-color,#5bc0de);border-radius:var(--button-border-radius,3px);border-style:var(--buttons-border-type,solid);border-width:2px;color:var(--button-normal-text-color,#69727d);margin-bottom:0;margin-top:40px;padding:var(--button-padding,12px 32px);vertical-align:middle}.elementor-widget-woocommerce-purchase-summary .woocommerce .order-again .button:hover{color:var(--button-hover-text-color,#69727d);transition-duration:var(--button-hover-transition-duration,.3s)}.elementor-widget-woocommerce-purchase-summary .woocommerce h2{color:var(--titles-color,#000);font-weight:400;margin-bottom:var(--titles-spacing,45px)}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-order-details h2{text-align:var(--order-summary-alignment,inherit)}.elementor-widget-woocommerce-purchase-summary .woocommerce .wc-bacs-bank-details-heading{text-align:var(--bank-details-alignment,inherit)}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-order-downloads__title{text-align:var(--downloads-alignment,inherit)}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-column--billing-address h2,.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-customer-details>h2{text-align:var(--billing-details-alignment,inherit)}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-column--shipping-address h2{text-align:var(--shipping-details-alignment,inherit)}.elementor-widget-woocommerce-purchase-summary .woocommerce a{color:var(--order-details-product-links-normal-color,#5bc0de)}.elementor-widget-woocommerce-purchase-summary .woocommerce a:hover{color:var(--order-details-product-links-hover-color,#5bc0de)}.elementor-widget-woocommerce-purchase-summary .woocommerce p{margin-bottom:20px}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-received{color:var(--confirmation-message-color,#69727d);display:var(--confirmation-message-display,none);margin-bottom:var(--sections-spacing,40px);text-align:var(--confirmation-message-alignment,inherit)}.elementor-widget-woocommerce-purchase-summary .woocommerce strong{color:var(--general-text-color,#000)}.elementor-widget-woocommerce-purchase-summary .woocommerce table tbody>tr:nth-child(odd)>td,.elementor-widget-woocommerce-purchase-summary .woocommerce table tbody>tr:nth-child(odd)>th{background-color:transparent}.elementor-widget-woocommerce-purchase-summary .woocommerce address{background-color:var(--sections-background-color,#fff);border-color:var(--sections-border-color,#d5d8dc);border-radius:var(--sections-border-radius,3px);border-style:var(--sections-border-type,solid);border-width:1px;color:var(--general-text-color,#69727d);padding:var(--sections-padding,15px 30px)}@media (max-width:767px){.elementor-widget-woocommerce-purchase-summary .woocommerce-column--2{margin-top:2em}}.elementor-widget-woocommerce-purchase-summary .woocommerce .woocommerce-thankyou-order-details+p{color:var(--general-text-color,#69727d)}.elementor-widget-woocommerce-purchase-summary .woocommerce .wc-bacs-bank-details-account-name{color:var(--account-title-color,#000);font-size:14px;font-weight:700;margin-bottom:var(--account-title-spacing,1rem)}.elementor-widget-woocommerce-purchase-summary .woocommerce section{margin-top:var(--sections-spacing,4em)}.elementor-widget-woocommerce-purchase-summary .woocommerce .wc-bacs-bank-details:last-child{margin-bottom:0}.elementor-widget-woocommerce-purchase-summary .woocommerce-table__line-item.order_item .woocommerce-table__product-name.product-purchase-note-is-below,.elementor-widget-woocommerce-purchase-summary .woocommerce-table__line-item.order_item .woocommerce-table__product-total.product-total.product-purchase-note-is-below{padding-bottom:0}.elementor-widget-woocommerce-purchase-summary .woocommerce-table--order-details{table-layout:fixed}.elementor-widget-woocommerce-purchase-summary .woocommerce-table--order-details td{word-wrap:break-word}(function(window, factory) { var globalInstall = function(){ factory(window.lazySizes); window.removeEventListener('lazyunveilread', globalInstall, true); }; factory = factory.bind(null, window, window.document); if(typeof module == 'object' && module.exports){ factory(require('lazysizes')); } else if (typeof define == 'function' && define.amd) { define(['lazysizes'], factory); } else if(window.lazySizes) { globalInstall(); } else { window.addEventListener('lazyunveilread', globalInstall, true); } }(window, function(window, document, lazySizes) { /*jshint eqnull:true */ 'use strict'; var regBgUrlEscape; if(document.addEventListener){ regBgUrlEscape = /\(|\)|\s|'/; addEventListener('lazybeforeunveil', function(e){ if(e.detail.instance != lazySizes){return;} var bg, bgWebP; if(!e.defaultPrevented) { if(e.target.preload == 'none'){ e.target.preload = 'auto'; } // handle data-back (so as not to conflict with the stock data-bg) bg = e.target.getAttribute('data-back'); if (bg) { if(ewww_webp_supported) { console.log('checking for data-back-webp'); bgWebP = e.target.getAttribute('data-back-webp'); if (bgWebP) { console.log('replacing data-back with data-back-webp'); bg = bgWebP; } } var dPR = (window.devicePixelRatio || 1); var targetWidth = Math.round(e.target.offsetWidth * dPR); var targetHeight = Math.round(e.target.offsetHeight * dPR); if ( 0 === bg.search(/\[/) ) { } else if (!shouldAutoScale(e.target)||!shouldAutoScale(e.target.parentNode)){ } else if (window.lazySizes.hC(e.target,'wp-block-cover')) { console.log('found wp-block-cover with data-back'); if (window.lazySizes.hC(e.target,'has-parallax')) { console.log('also has-parallax with data-back'); targetWidth = Math.round(window.screen.width * dPR); targetHeight = Math.round(window.screen.height * dPR); } else if (targetHeight<300) { targetHeight = 430; } bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover'); } else if (window.lazySizes.hC(e.target,'cover-image')){ console.log('found .cover-image with data-back'); bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover'); } else if (window.lazySizes.hC(e.target,'elementor-bg')){ console.log('found elementor-bg with data-back'); bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover'); } else if (window.lazySizes.hC(e.target,'et_parallax_bg')){ console.log('found et_parallax_bg with data-back'); bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover'); } else if (window.lazySizes.hC(e.target,'bg-image-crop')){ console.log('found bg-image-crop with data-back'); bg = constrainSrc(bg,targetWidth,targetHeight,'bg-cover'); } else { console.log('found other data-back'); bg = constrainSrc(bg,targetWidth,targetHeight,'bg'); } if ( e.target.style.backgroundImage && -1 === e.target.style.backgroundImage.search(/^initial/) ) { // Convert JSON for multiple URLs. if ( 0 === bg.search(/\[/) ) { console.log('multiple URLs to append'); bg = JSON.parse(bg); bg.forEach( function(bg_url){ bg_url = (regBgUrlEscape.test(bg_url) ? JSON.stringify(bg_url) : bg_url ); } ); bg = 'url("' + bg.join('"), url("') + '"'; var new_bg = e.target.style.backgroundImage + ', ' + bg; console.log('setting .backgroundImage: ' + new_bg ); e.target.style.backgroundImage = new_bg; } else { console.log( 'appending bg url: ' + e.target.style.backgroundImage + ', url(' + (regBgUrlEscape.test(bg) ? JSON.stringify(bg) : bg ) + ')' ); e.target.style.backgroundImage = e.target.style.backgroundImage + ', url("' + (regBgUrlEscape.test(bg) ? JSON.stringify(bg) : bg ) + '")'; } } else { // Convert JSON for multiple URLs. if ( 0 === bg.search(/\[/) ) { console.log('multiple URLs to insert'); bg = JSON.parse(bg); bg.forEach( function(bg_url){ bg_url = (regBgUrlEscape.test(bg_url) ? JSON.stringify(bg_url) : bg_url ); } ); bg = 'url("' + bg.join('"), url("') + '"'; console.log('setting .backgroundImage: ' + bg ); e.target.style.backgroundImage = bg; } else { console.log('setting .backgroundImage: ' + 'url(' + (regBgUrlEscape.test(bg) ? JSON.stringify(bg) : bg ) + ')'); e.target.style.backgroundImage = 'url(' + (regBgUrlEscape.test(bg) ? JSON.stringify(bg) : bg ) + ')'; } } } } }, false); } })); img { max-width: 100%; height: auto; display: block; border: 0; } table { border-collapse: collapse; mso-table-lspace: 0; mso-table-rspace: 0; } td, th { word-break: break-word; overflow-wrap: anywhere; } p, h1, h2, h3, h4, h5, h6 { margin: 0 0 .75em; } /** * Save manager - handleInlineSave and showInlineSuccess * * Reads (via globals set by earlier modules): * SFE.Context - .activeEditor (r/w), .draftEditState (r/w), * .pageRevisionToken (r/w), .uuidMap, * .actionBar * SFE.ElementState - .ElementState * SFE.Api - .apiCall * SFE.BlockSerializer - .buildBlockPayload * SFE.ListBlockTracker * SFE.ElementUpdater - .applyNewHTML * SFE.TIMING * SFE.SaveHelpers - .setButtonLoading, .clearButtonLoading, * .lockSaveUI, .unlockSaveUI, * .createSuccessElement, * .handleRevisionConflict, * .updatePageRevisionToken * SFE.PostLockManager - .ensureLock * SFE.ManagerData - .postId, .permissions * SFE.restoreElementContent - set by EditorLifecycle.js * SFE.rebindChildren - set by EditorLifecycle.js * SFE.attachActionBarToElement - set by frontend-inline-edit.js (via HoverManager) * * Exposes: SFE.SaveManager { handleInlineSave, showInlineSuccess } */ (function() { 'use strict'; window.MWP = window.MWP || {}; window.MWP.SFE = window.MWP.SFE || {}; const SFE = window.MWP.SFE; SFE.ManagerData = SFE.ManagerData || {}; function getSaveHooks() { return SFE.SaveHooks || null; } function getProDraftApi() { return SFE.PRO?.DraftApi || null; } function resolveSaveStrategy(editorState) { let strategy = editorState?.saveStrategy || 'single'; const hooks = getSaveHooks(); if (hooks && typeof hooks.resolveStrategy === 'function') { try { const override = hooks.resolveStrategy({ strategy, editorState }); if (override === 'single' || override === 'batch') { strategy = override; } } catch (error) { console.warn('FrontEdit: save strategy hook failed', error); } } return strategy; } async function runSaveHook(hookName, payload) { const hooks = getSaveHooks(); if (!hooks || typeof hooks[hookName] !== 'function') return; try { await hooks[hookName](payload); } catch (error) { console.warn(`FrontEdit: save hook ${hookName} failed`, error); } } /** * Read the cached published baseline HTML for a block, if one has been * captured during this page lifecycle. * * @param {string} uuid Block UUID. * @returns {string} Cached outer HTML snapshot or an empty string. */ function getPublishedBaselineHTML(uuid) { if (!uuid || !SFE.Context?.uuidMap?.[uuid]) return ''; const cached = SFE.Context.uuidMap[uuid].publishedBaselineOuterHTML; return (typeof cached === 'string' && cached.trim()) ? cached : ''; } /** * Restore the draft-submit display element from the cached published * baseline when available, falling back to the per-editor restore snapshot. * * @param {Object} editorState Editor state for the active save session. * @returns {Element} The restored element. */ function restoreDraftSubmittedElement(editorState) { const baselineHTML = getPublishedBaselineHTML(editorState?.uuid); if (!baselineHTML) { return SFE.restoreElementContent(editorState.element, editorState); } const temp = document.createElement('div'); temp.innerHTML = baselineHTML; const restoredElement = temp.firstElementChild; if (!restoredElement || !editorState?.element?.parentNode) { return SFE.restoreElementContent(editorState.element, editorState); } editorState.element.parentNode.replaceChild(restoredElement, editorState.element); return restoredElement; } /** * Destroy persisted block edit sessions after one successful save lifecycle. * * Saving ends the block session entirely, so any cached undo history for the * saved UUIDs must be discarded before the next editor open starts fresh. * * @param {string|string[]} uuids One UUID or a list of UUIDs to destroy. * @returns {void} */ function destroySavedBlockSessions(uuids) { const list = Array.isArray(uuids) ? uuids : [uuids]; if (typeof SFE.destroyPersistedBlockEditSessions === 'function') { SFE.destroyPersistedBlockEditSessions(list); return; } if (typeof SFE.destroyPersistedBlockEditSession !== 'function') { return; } list.forEach((uuid) => { SFE.destroyPersistedBlockEditSession(uuid); }); } async function handleInlineSave(editorState) { const ctx = SFE.Context; const publicApiBridge = SFE.PublicApiBridge || null; const { apiCall, ensureResolvedMediaAttributes } = SFE.Api; const { buildBlockPayload } = SFE.BlockSerializer; const { setButtonLoading, clearButtonLoading, lockSaveUI, unlockSaveUI, handleRevisionConflict, updatePageRevisionToken, fetchRenderedBlockHTML, reloadAfterRefreshFailure } = SFE.SaveHelpers; const TIMING = SFE.TIMING; const postId = SFE.ManagerData.postId; const perms = SFE.ManagerData.permissions || {}; const uuidMap = ctx.uuidMap; const batchManager = SFE.BatchEditManager || null; const proDraftApi = getProDraftApi(); const saveStrategy = resolveSaveStrategy(editorState); const strategyPayload = { strategy: saveStrategy, editorState }; if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:before', editorState, { source: 'sfe', saveStrategy, }); } await runSaveHook('beforeSave', strategyPayload); if ( saveStrategy === 'batch' && batchManager && typeof batchManager.isSessionActive === 'function' && batchManager.isSessionActive() ) { const saveBtn = editorState.actionsContainer?.querySelector('.mwp-sfe-btn-primary-inline') || null; const cancelBtn = editorState.actionsContainer?.querySelector('.mwp-sfe-btn-secondary-inline') || null; if (cancelBtn) cancelBtn.disabled = true; try { await batchManager.handleBatchSave(saveBtn); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:after', editorState, { source: 'sfe', saveStrategy, success: true, }); } await runSaveHook('afterSave', { ...strategyPayload, success: true }); } finally { // Re-enable the Cancel button in case save failed before showInlineSuccess could clean up the editor. if (cancelBtn) cancelBtn.disabled = false; } return; } // Build canonical block payload via wp.blocks API (serialize → parse round-trip) try { if (typeof ensureResolvedMediaAttributes === 'function') { await ensureResolvedMediaAttributes(editorState); } } catch (error) { console.error('FrontEdit: resolveMediaAttributes failed:', error); alert('Error saving: ' + error.message); return; } let payloadContent; try { const blockStructure = buildBlockPayload( editorState.element, editorState ); if ( !blockStructure ) { alert( 'Error saving: Could not build block structure. Please refresh and try again.' ); return; } payloadContent = JSON.stringify( blockStructure ); } catch ( error ) { console.error( 'FrontEdit: buildBlockPayload failed:', error ); alert( 'Error saving: ' + error.message ); return; } // Show loading state const saveBtn = editorState.actionsContainer.querySelector('.mwp-sfe-btn-primary-inline'); const cancelBtn = editorState.actionsContainer.querySelector('.mwp-sfe-btn-secondary-inline'); const originalText = saveBtn.textContent; lockSaveUI(saveBtn); if (cancelBtn) cancelBtn.disabled = true; // Check if we're editing a draft const draftEditState = ctx.draftEditState; const isEditingDraft = draftEditState && draftEditState.draftElement === editorState.element; saveBtn.textContent = perms.can_publish ? 'Saving...' : 'Submitting...'; // Helper to restore save button to clickable state on any failure path const restoreSaveBtn = () => { unlockSaveUI(saveBtn); saveBtn.textContent = originalText; if (cancelBtn) cancelBtn.disabled = false; }; // Handle based on permissions and draft state if (perms.can_publish) { if (!await SFE.PostLockManager.ensureLock()) { restoreSaveBtn(); return; } try { if (isEditingDraft && draftEditState.version) { if (!proDraftApi || typeof proDraftApi.approveWithEdit !== 'function') { throw new Error('Draft approve-with-edit is unavailable.'); } const approveResult = await proDraftApi.approveWithEdit({ post_id: postId, element_uuid: editorState.uuid, version: draftEditState.version, new_content: payloadContent, page_revision_token: ctx.pageRevisionToken // conflict detection }); // Update token so subsequent saves don't false-conflict against our own revision. updatePageRevisionToken(approveResult); ctx.draftEditState = null; } else { const applyResult = await apiCall('/apply', { post_id: postId, element_uuid: editorState.uuid, handler_id: editorState.handler.id, edit_content: payloadContent, page_revision_token: ctx.pageRevisionToken }); // Update token to the revision the plugin just created, so subsequent // inline saves in this session don't false-conflict against our own revision. updatePageRevisionToken(applyResult); } const refreshedHTML = await fetchRenderedBlockHTML(editorState.uuid, { force: true }); if (refreshedHTML) { destroySavedBlockSessions(editorState.uuid); showInlineSuccess(editorState, 'Changes Saved', refreshedHTML); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:after', editorState, { source: 'sfe', saveStrategy, success: true, }); } await runSaveHook('afterSave', { ...strategyPayload, success: true }); } else { reloadAfterRefreshFailure('FrontEdit: Save succeeded but refreshed block HTML was unavailable.'); return; } } catch (error) { if (error.message === 'POST_LOCKED') { await SFE.PostLockManager?.handleLockedError(error); restoreSaveBtn(); return; } if (error.message === 'BLOCK_HTML_REFRESH_FAILED') { reloadAfterRefreshFailure('FrontEdit: Save succeeded but refreshed block HTML could not be extracted from the post render.'); return; } if (error.message === 'REVISION_CONFLICT') { if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:error', editorState, { source: 'sfe', saveStrategy, message: 'REVISION_CONFLICT', }); } const shouldRetry = await handleRevisionConflict(restoreSaveBtn); if (!shouldRetry) return; // Retry without token - server skips conflict check when token is absent. // Use the same endpoint that originally triggered the conflict. try { if (isEditingDraft && draftEditState.version) { if (!proDraftApi || typeof proDraftApi.approveWithEdit !== 'function') { throw new Error('Draft approve-with-edit is unavailable.'); } await proDraftApi.approveWithEdit({ post_id: postId, element_uuid: editorState.uuid, version: draftEditState.version, new_content: payloadContent // No token - force approve }); ctx.draftEditState = null; } else { await apiCall('/apply', { post_id: postId, element_uuid: editorState.uuid, handler_id: editorState.handler.id, edit_content: payloadContent // No token - force save }); } const refreshedHTML = await fetchRenderedBlockHTML(editorState.uuid, { force: true }); if (refreshedHTML) { destroySavedBlockSessions(editorState.uuid); showInlineSuccess( editorState, 'Changes Saved', refreshedHTML, false, 'success', { reloadAfterSuccess: true } ); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:after', editorState, { source: 'sfe', saveStrategy, success: true, }); } await runSaveHook('afterSave', { ...strategyPayload, success: true }); } else { reloadAfterRefreshFailure('FrontEdit: Retry save succeeded but refreshed block HTML was unavailable.'); return; } } catch (retryError) { if (retryError.message === 'BLOCK_HTML_REFRESH_FAILED') { reloadAfterRefreshFailure('FrontEdit: Retry save succeeded but refreshed block HTML could not be extracted from the post render.'); return; } console.error('Save failed on retry:', retryError); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:error', editorState, { source: 'sfe', saveStrategy, message: String(retryError.message || 'SAVE_FAILED'), }); } if ( retryError.message && retryError.message.indexOf( '403' ) !== -1 ) { alert( 'You no longer have permission to save. Your publish access may have been changed. Please refresh the page.' ); } else { alert('Error: ' + retryError.message); } restoreSaveBtn(); } return; } console.error('Save failed:', error); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:error', editorState, { source: 'sfe', saveStrategy, message: String(error.message || 'SAVE_FAILED'), }); } if ( error.message && error.message.indexOf( '403' ) !== -1 ) { alert( 'You no longer have permission to save. Your publish access may have been changed. Please refresh the page.' ); } else { alert('Error: ' + error.message); } restoreSaveBtn(); } } else if (perms.can_draft) { try { if (!proDraftApi || typeof proDraftApi.submitDraft !== 'function') { throw new Error('Draft submission is unavailable.'); } const data = await proDraftApi.submitDraft({ post_id: postId, element_uuid: editorState.uuid, handler_id: editorState.handler.id, edit_content: payloadContent }); editorState.element.classList.add('mwp-sfe-status-pending'); if (uuidMap[editorState.uuid]) { uuidMap[editorState.uuid].is_pending = true; uuidMap[editorState.uuid].pending_info = { version: data.entry.version, user: data.entry.user_name, date: new Date(data.entry.timestamp * 1000).toLocaleString() }; } showInlineSuccess( editorState, `Draft Submitted (Version ${data.entry.version})`, null, true, 'warning' ); destroySavedBlockSessions(editorState.uuid); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:after', editorState, { source: 'sfe', saveStrategy, success: true, }); } await runSaveHook('afterSave', { ...strategyPayload, success: true }); } catch (error) { console.error('Draft submit failed:', error); if (publicApiBridge) { publicApiBridge.emitSaveEvent('save:error', editorState, { source: 'sfe', saveStrategy, message: String(error.message || 'SAVE_FAILED'), }); } alert('Error: ' + error.message); restoreSaveBtn(); } } } /** * Show success message and update element * Handles both simple comments and complex save/draft operations */ function showInlineSuccess(editorState, message, newHTML, isDraft = false, variant = 'success', options = {}) { const ctx = SFE.Context; const TIMING = SFE.TIMING; const ListBlockTracker = SFE.ListBlockTracker; const { ElementState } = SFE.ElementState; const { createSuccessElement } = SFE.SaveHelpers; const { applyNewHTML } = SFE.ElementUpdater; const actionBar = ctx.actionBar; const uuidMap = ctx.uuidMap; const shouldReloadAfterSuccess = !!options.reloadAfterSuccess; const reloadAfterSuccess = shouldReloadAfterSuccess && SFE.SaveHelpers && typeof SFE.SaveHelpers.reloadPageWithGuardBypass === 'function' ? SFE.SaveHelpers.reloadPageWithGuardBypass : null; // Detect if this is a simple comment (minimal editorState) vs complex save/draft const isComment = !editorState.actionsContainer; // Create and position success message (common to all types) const successDiv = createSuccessElement(message, variant); document.body.appendChild(successDiv); // Force a reflow before allowing transitions again successDiv.offsetHeight; successDiv.style.transition = ''; // === SIMPLE PATH: Comments === if (isComment) { // Comment cleanup already done by exitCommentMode // Just show message, wait, and fade out setTimeout(() => { successDiv.style.opacity = '0'; setTimeout(() => successDiv.remove(), TIMING.SUCCESS_FADE); }, TIMING.SUCCESS_DISPLAY); return; } // === COMPLEX PATH: Save/Draft Operations === // Remove any wp-elements-* styles injected into for this draft preview. if (editorState.uuid) { document.querySelectorAll(`style[data-mwp-sfe-draft-uuid="${editorState.uuid}"]`) .forEach(el => el.remove()); } // Store reference to this editor session for validation const thisEditorSession = editorState; // Cleanup editor resources if (editorState.resizeObserver) { editorState.resizeObserver.disconnect(); } if (editorState.toolbarContainer) editorState.toolbarContainer.remove(); if (editorState.actionsContainer) { editorState.actionsContainer.style.display = 'none'; } if (editorState.previewOverlay) editorState.previewOverlay.remove(); if (editorState.updatePositions) { window.removeEventListener('scroll', editorState.updatePositions, true); window.removeEventListener('resize', editorState.updatePositions); } if (editorState.updatePreviewPosition) { window.removeEventListener('scroll', editorState.updatePreviewPosition, true); } if (editorState.escapeHandler) { document.removeEventListener('keydown', editorState.escapeHandler); } if (editorState.cleanupFocus) editorState.cleanupFocus(); const switchTarget = editorState._mwpComponentSwitchTarget || editorState.element || null; if (editorState.componentSwitchHandler && switchTarget) { switchTarget.removeEventListener('mousedown', editorState.componentSwitchHandler, true); delete editorState.componentSwitchHandler; } if (editorState.componentClickGuard && switchTarget) { switchTarget.removeEventListener('click', editorState.componentClickGuard, true); delete editorState.componentClickGuard; } if (editorState.componentTabHandler && switchTarget) { switchTarget.removeEventListener('keydown', editorState.componentTabHandler, true); delete editorState.componentTabHandler; } delete editorState._mwpComponentSwitchTarget; if (editorState._mwpSchemaMediaSession && typeof editorState._mwpSchemaMediaSession.cleanup === 'function') { editorState._mwpSchemaMediaSession.cleanup({ preserveChanges: true }); delete editorState._mwpSchemaMediaSession; } if (Array.isArray(editorState.editableComponents)) { const elementPrep = SFE.ElementPrep || null; const textEditorHost = SFE.SchemaEditorHost?.resolveTextEditorHost?.(editorState) || null; editorState.editableComponents.forEach(component => { if (!component || !component.element) return; component.element.classList.remove( 'mwp-sfe-inline-editor', 'mwp-sfe-component-active', 'mwp-sfe-editable-component' ); if (elementPrep && typeof elementPrep.pruneEmptyClassAttribute === 'function') { elementPrep.pruneEmptyClassAttribute(component.element); } component.element.removeAttribute('contenteditable'); component.element.removeAttribute('spellcheck'); component.element.removeAttribute('data-mwp-sfe-editable-component'); component.element.removeAttribute('data-mwp-sfe-active-component'); if (textEditorHost && component.element._mwpEditor === textEditorHost) { delete component.element._mwpEditor; } }); } // Cleanup list tracker if (editorState.listTracker) { ListBlockTracker.destroy(editorState.listTracker); delete editorState.listTracker; } // Cleanup media focus manager (stored on bar, not editorState) if (editorState.actionsContainer && editorState.actionsContainer._cleanupMediaFocus) { editorState.actionsContainer._cleanupMediaFocus(); delete editorState.actionsContainer._cleanupMediaFocus; } if (editorState.textarea) editorState.textarea.remove(); if (editorState.contentWrapper) editorState.contentWrapper.remove(); // DON'T re-enable other elements yet - keep them disabled during animation // Update element content based on operation type if (isDraft) { // For drafts: restore the block to the cached published baseline when // available so repeated local draft edits do not overwrite the live // visitor state shown after submission. Fall back to the editor's // session snapshot if the baseline was never captured. const restoredElement = restoreDraftSubmittedElement(editorState); // Remove editing-active but KEEP element-active to prevent hover during success display. // IMPORTANT: restoreElementContent reconstructs from originalOuterHTML which never had // mwp-sfe-element-active. Without explicitly re-adding it here, the click guard // (document.querySelector('.mwp-sfe-element-active')) immediately returns null, // allowing the user to click any other element and interrupt the cleanup sequence. restoredElement.classList.remove('mwp-sfe-editing-active'); restoredElement.classList.add('mwp-sfe-element-active'); if (editorState.clearPending) { // Discard path: draft was rejected - element is no longer pending. restoredElement.classList.remove('mwp-sfe-status-pending'); // Also clear uuidMap so the element becomes fully editable again. const uuidEntry = uuidMap[editorState.uuid]; if (uuidEntry) { uuidEntry.is_pending = false; delete uuidEntry.pending_info; } } else { // Save-as-draft path: element is still pending review. restoredElement.classList.add('mwp-sfe-status-pending'); } // Update reference for later use editorState.element = restoredElement; // NOTE: rebindChildren is intentionally deferred to the post-enableAllElements // requestAnimationFrame below - same reasoning as the non-draft path (Bug 1 fix): // children are new DOM nodes not in disabledElements and would become immediately // interactive while the success banner is still visible. } else { // For published: Replace the entire element with server-rendered HTML. // applyNewHTML handles the DOM swap, UUID attributes, and uuidMap cleanup. const newElement = applyNewHTML(editorState.element, editorState.uuid, newHTML); if (newElement) { // Keep element marked as active during success display to prevent hover. newElement.classList.add('mwp-sfe-element-active'); // Update reference for reattaching action bar editorState.element = newElement; // NOTE: Do NOT call rebindChildren here. The new element's inner blocks // are fresh DOM nodes not in disabledElements, so they'd become interactive // immediately while the success animation is still running. Rebind is deferred // to the final cleanup block below, after enableAllElements fires. } } // Handle additional elements supplied by batch save. // Each item: { element, uuid, html } - the other dirty blocks beyond the active editor. // These have no open editor to clean up; they only need a DOM swap + lock during banner. const additionalNewEls = []; const additionalItems = options.additionalElements || []; for (const item of additionalItems) { if (!item.element || !item.uuid || !item.html) continue; const newEl = applyNewHTML(item.element, item.uuid, item.html); if (!newEl) continue; // Lock during success banner (same as the primary element above). newEl.classList.add('mwp-sfe-element-active'); if (isDraft) { newEl.classList.add('mwp-sfe-status-pending'); if (uuidMap[item.uuid]) { uuidMap[item.uuid].is_pending = true; } } additionalNewEls.push(newEl); } // Animate and final cleanup // Store timeout ID for potential cleanup const successTimeoutId = setTimeout(() => { successDiv.style.opacity = '0'; setTimeout(() => { successDiv.remove(); // Only cleanup if no new editor session has started // We check if UUIDs match. Media editing uses a "mock" state object for saving // that is a different object reference than activeEditor, so strictly checking (===) fails. const activeEditor = ctx.activeEditor; const isMatchingSession = activeEditor && (activeEditor === thisEditorSession || activeEditor.uuid === thisEditorSession.uuid); if (activeEditor === null || isMatchingSession) { // Now re-enable other elements ElementState.enableAllElements(); // Centralized unlock let triggerBtn = null; if (editorState && editorState.actionsContainer) { triggerBtn = editorState.actionsContainer.querySelector('.mwp-sfe-btn-primary-inline'); } if (SFE.SaveHelpers && SFE.SaveHelpers.unlockSaveUI) { SFE.SaveHelpers.unlockSaveUI(triggerBtn); } else { ctx.isSaving = false; } // Create cleanup callback (none needed for success state) const successCleanup = () => { // Hide bar using consolidated system if (editorState.actionsContainer) { actionBar.hide(); } }; // Remove active class now that we're resetting editorState.element.classList.remove('mwp-sfe-element-active'); // Use consolidated exit logic (will handle restoration and smart hiding) actionBar.reset( editorState.actionsContainer, editorState.element, editorState.uuid, successCleanup ); // Reattach click/hover action bar to the (possibly new) element // and rebind inner blocks NOW - after enableAllElements has fired, // so they enter the enabled state along with everything else. requestAnimationFrame(() => { try { SFE.rebindChildren(editorState.element); SFE.attachActionBarToElement(editorState.element); } catch (err) { console.error('FrontEdit: failed to reattach action bar after save', err); } // Rebind additional elements from batch save (if any). for (const el of additionalNewEls) { el.classList.remove('mwp-sfe-element-active'); try { SFE.rebindChildren(el); SFE.attachActionBarToElement(el); } catch (err) { console.error('FrontEdit: failed to rebind batch element after save', err); } } }); // Force clear activeEditor if it matches this session if (isMatchingSession) { ctx.activeEditor = null; } if (reloadAfterSuccess) { reloadAfterSuccess(); return; } } // If a new editor session started, don't interfere }, TIMING.SUCCESS_FADE); }, TIMING.SUCCESS_DISPLAY); // Store timeout ID on editorState for cleanup if (editorState) { editorState._successTimeoutId = successTimeoutId; } } SFE.SaveManager = { handleInlineSave, showInlineSuccess }; })();