@@ -388,6 +388,8 @@ let editorMode = 'basic';
388388let activeEditorSection = 'general' ;
389389let advancedSectionCollapsedState = { } ;
390390let memberBulkMoveInFlight = false ;
391+ let memberBulkMoveUndoState = null ;
392+ let memberBulkMoveUndoInFlight = false ;
391393const SMART_DEFAULT_FIELD_NAMES = new Set ( [
392394 'icon' ,
393395 'preview' ,
@@ -4383,6 +4385,11 @@ function syncMemberSnapshotBaseline() {
43834385 updateChangeSummaryPanel ( ) ;
43844386}
43854387
4388+ function clearMemberBulkMoveUndoState ( ) {
4389+ memberBulkMoveUndoState = null ;
4390+ memberBulkMoveUndoInFlight = false ;
4391+ }
4392+
43864393const applyMemberBulkMoveResultLocally = ( targetFolderId , movedNames = [ ] ) => {
43874394 const safeTargetFolderId = String ( targetFolderId || '' ) . trim ( ) ;
43884395 const uniqueNames = Array . from ( new Set ( ( Array . isArray ( movedNames ) ? movedNames : [ ] ) . map ( ( name ) => String ( name || '' ) . trim ( ) ) . filter ( Boolean ) ) ) ;
@@ -4451,42 +4458,37 @@ const restoreEditorBulkMoveBackup = async (backupName) => {
44514458 return response . restore || { } ;
44524459} ;
44534460
4454- const offerMemberBulkMoveUndo = async ( backup , successMessage ) => {
4455- const backupName = String ( backup ?. name || '' ) . trim ( ) ;
4456- if ( ! backupName ) {
4461+ function setMemberBulkMoveUndoState ( backup , successMessage ) {
4462+ memberBulkMoveUndoState = {
4463+ backupName : String ( backup ?. name || '' ) . trim ( ) ,
4464+ message : String ( successMessage || '' ) . trim ( ) || 'Bulk move complete.'
4465+ } ;
4466+ memberBulkMoveUndoInFlight = false ;
4467+ }
4468+
4469+ async function undoEditorMemberBulkMove ( ) {
4470+ const backupName = String ( memberBulkMoveUndoState ?. backupName || '' ) . trim ( ) ;
4471+ if ( ! backupName || memberBulkMoveUndoInFlight ) {
4472+ return false ;
4473+ }
4474+ memberBulkMoveUndoInFlight = true ;
4475+ updateMemberBulkMoveUi ( ) ;
4476+ try {
4477+ await restoreEditorBulkMoveBackup ( backupName ) ;
4478+ suppressUnloadPrompt = true ;
4479+ location . reload ( ) ;
4480+ return true ;
4481+ } catch ( error ) {
4482+ memberBulkMoveUndoInFlight = false ;
4483+ updateMemberBulkMoveUi ( ) ;
44574484 swal ( {
4458- title : 'Bulk move complete ' ,
4459- text : successMessage ,
4460- type : 'success '
4485+ title : 'Undo failed ' ,
4486+ text : extractAjaxErrorMessage ( error , 'bulk move undo' ) ,
4487+ type : 'error '
44614488 } ) ;
4462- return ;
4489+ return false ;
44634490 }
4464- swal ( {
4465- title : 'Bulk move complete' ,
4466- text : `${ successMessage } \n\nBackup created: ${ backupName } \nUndo will reload this editor.` ,
4467- type : 'success' ,
4468- showCancelButton : true ,
4469- confirmButtonText : 'Undo' ,
4470- cancelButtonText : 'Close' ,
4471- closeOnConfirm : false ,
4472- showLoaderOnConfirm : true
4473- } , async ( confirmed ) => {
4474- if ( ! confirmed ) {
4475- return ;
4476- }
4477- try {
4478- await restoreEditorBulkMoveBackup ( backupName ) ;
4479- suppressUnloadPrompt = true ;
4480- location . reload ( ) ;
4481- } catch ( error ) {
4482- swal ( {
4483- title : 'Undo failed' ,
4484- text : extractAjaxErrorMessage ( error , 'bulk move undo' ) ,
4485- type : 'error'
4486- } ) ;
4487- }
4488- } ) ;
4489- } ;
4491+ }
44904492
44914493async function applyEditorMemberBulkMove ( ) {
44924494 if ( memberBulkMoveInFlight ) {
@@ -4573,6 +4575,7 @@ async function applyEditorMemberBulkMove() {
45734575 return ;
45744576 }
45754577 memberBulkMoveInFlight = true ;
4578+ clearMemberBulkMoveUndoState ( ) ;
45764579 updateMemberBulkMoveUi ( ) ;
45774580 try {
45784581 const preludeLines = sharedApi . buildBulkAssignmentPreludeLines ( plan , {
@@ -4596,13 +4599,14 @@ async function applyEditorMemberBulkMove() {
45964599 } ) ;
45974600 if ( executionResult ?. cancelled ) {
45984601 $ ( '#fvMemberBulkSummary' ) . text ( executionResult . summary || 'Bulk move is already running.' ) ;
4602+ swal . close ( ) ;
45994603 return ;
46004604 }
4605+ swal . close ( ) ;
46014606 applyMemberBulkMoveResultLocally ( plan . targetFolderId , executionResult ?. lines ?. filter ( ( entry ) => entry . status === 'success' ) . map ( ( entry ) => entry . name ) || [ ] ) ;
46024607 const successMessage = executionResult ?. summary || `Moved ${ plan . actionableNames . length } item${ plan . actionableNames . length === 1 ? '' : 's' } .` ;
4603- $ ( '#fvMemberBulkSummary' ) . text ( successMessage ) ;
4604- swal . close ( ) ;
4605- await offerMemberBulkMoveUndo ( executionResult ?. backup || null , successMessage ) ;
4608+ setMemberBulkMoveUndoState ( executionResult ?. backup || null , successMessage ) ;
4609+ updateMemberBulkMoveUi ( ) ;
46064610 } catch ( error ) {
46074611 $ ( '#fvMemberBulkSummary' ) . text ( 'Bulk move failed.' ) ;
46084612 swal ( {
@@ -4636,6 +4640,26 @@ function updateMemberBulkMoveUi() {
46364640 if ( memberBulkMoveInFlight ) {
46374641 return ;
46384642 }
4643+ if ( memberBulkMoveUndoState ) {
4644+ const successText = escapeHtml ( String ( memberBulkMoveUndoState . message || 'Bulk move complete.' ) ) ;
4645+ const backupName = escapeHtml ( String ( memberBulkMoveUndoState . backupName || '' ) . trim ( ) ) ;
4646+ if ( memberBulkMoveUndoInFlight && backupName ) {
4647+ summaryNode . html ( `<span class="fv-member-bulk-summary-text">${ successText } Restoring backup ${ backupName } ...</span>` ) ;
4648+ return ;
4649+ }
4650+ if ( backupName ) {
4651+ summaryNode . html ( `
4652+ <span class="fv-member-bulk-summary-text">${ successText } Backup created: ${ backupName } .</span>
4653+ <button type="button" id="fvMemberBulkUndo" class="fv-member-bulk-inline-action">Undo</button>
4654+ ` ) ;
4655+ $ ( '#fvMemberBulkUndo' ) . off ( 'click' ) . on ( 'click' , ( ) => {
4656+ void undoEditorMemberBulkMove ( ) ;
4657+ } ) ;
4658+ return ;
4659+ }
4660+ summaryNode . html ( `<span class="fv-member-bulk-summary-text">${ successText } </span>` ) ;
4661+ return ;
4662+ }
46394663 if ( ( scopeDetails . movableCount || 0 ) <= 0 ) {
46404664 summaryNode . text ( scopeDetails . skippedRegexNames . length > 0
46414665 ? 'Current scope only contains regex-controlled members. Those stay controlled by rules.'
0 commit comments