@atlaskit/editor-plugin-synced-block
Version:
SyncedBlock plugin for @atlaskit/editor-core
102 lines (100 loc) • 5.26 kB
JavaScript
import { pmHistoryPluginKey } from '@atlaskit/editor-common/utils';
import { FLAG_ID } from '../../types';
import { syncedBlockPluginKey } from '../main';
import { recomputeDeleteTransaction, recomputeUnsyncTransaction } from './recompute-delete-transaction';
const onRetry = (api, syncBlockStore) => () => {
var _api$core;
api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
tr
}) => {
return tr.setMeta(syncedBlockPluginKey, {
bodiedSyncBlockDeletionStatus: 'processing',
activeFlag: false
});
});
syncBlockStore.sourceManager.retryDeletion();
};
const onDismissed = syncBlockStore => tr => {
syncBlockStore.sourceManager.clearPendingDeletion();
return tr.setMeta(syncedBlockPluginKey, {
bodiedSyncBlockDeletionStatus: 'none'
});
};
export const handleBodiedSyncBlockRemoval = (bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, deletionReason, mechanism, feedbackCallbacks) => {
// Clear potential old pending deletion to retreat the deletion as first attempt
syncBlockStore.sourceManager.clearPendingDeletion();
// If there are source sync blocks being removed, and we need to confirm with user before deleting,
// we block the transaction here, and wait for user confirmation to proceed with deletion.
// See editor-common/src/sync-block/sync-block-store-manager.ts for how we handle user confirmation and
// proceed with deletion.
syncBlockStore.sourceManager.deleteSyncBlocksWithConfirmation(bodiedSyncBlockRemoved.map(node => node.attrs), deletionReason, () => {
var _api$core2;
// Recompute the delete fresh from the live document instead of
// replaying a stashed-and-rebased transaction. The stash/rebase
// approach produced stale, schema-invalid transactions when the
// document changed shape (local edits or remote collab) while the
// confirmation modal was open — the dominant signature being
// "Invalid content for node bodiedSyncBlock: <>". See EDITOR-7889.
api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({
tr
}) => {
// EDITOR-8230: unsync must remove only the sync wrapper and keep the block's
// content inline, whereas delete removes the whole block. Before this branch,
// the confirm path always recomputed a full delete, so unsyncing a source block
// silently deleted its content. When the deletion was actually triggered by
// unsync, recompute an unwrap (replaceWith content) instead of a delete.
const recomputedTr = deletionReason === 'source-block-unsynced' ? recomputeUnsyncTransaction(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(node => node.attrs)) : recomputeDeleteTransaction(tr, syncBlockStore.sourceManager.isSourceBlock, bodiedSyncBlockRemoved.map(node => node.attrs));
// The target node(s) no longer exist in the live document (e.g.
// a remote collaborator already removed them). There is nothing
// to delete locally, so return `null` to skip the dispatch
// entirely — dispatching the untouched transaction would be a
// no-op that still runs the plugin's filter/append hooks. The
// backend deletion has already been issued by the store manager.
if (!recomputedTr) {
return null;
}
recomputedTr.setMeta('isConfirmedSyncBlockDeletion', true);
feedbackCallbacks === null || feedbackCallbacks === void 0 ? void 0 : feedbackCallbacks.onDeleteTransaction(recomputedTr);
if (!recomputedTr.getMeta(pmHistoryPluginKey)) {
// bodiedSyncBlock deletion is expected to be permanent (cannot undo)
// For a normal deletion (not triggered by undo), remove it from history so that it cannot be undone
recomputedTr.setMeta('addToHistory', false);
}
return recomputedTr;
});
}, success => {
var _api$core3;
feedbackCallbacks === null || feedbackCallbacks === void 0 ? void 0 : feedbackCallbacks.onDeleteCompleted(success);
api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(({
tr
}) => {
let newState;
if (!success) {
newState = {
activeFlag: {
id: FLAG_ID.FAIL_TO_DELETE,
onRetry: onRetry(api, syncBlockStore),
onDismissed: onDismissed(syncBlockStore)
}
};
} else {
newState = {
activeFlag: false
};
}
newState = {
...newState,
bodiedSyncBlockDeletionStatus: syncBlockStore.sourceManager.isRetryingDeletion() ?
// For retry, reset to none directly to clean up the status
'none' :
// For the first attempt, set to completed for deletion modal can close the modal
'completed'
};
return tr.setMeta(syncedBlockPluginKey, newState);
});
}, () => {
confirmationTransactionRef.current = undefined;
feedbackCallbacks === null || feedbackCallbacks === void 0 ? void 0 : feedbackCallbacks.onDestroy();
}, mechanism);
return false;
};