@atlaskit/editor-plugin-synced-block
Version:
SyncedBlock plugin for @atlaskit/editor-core
362 lines (355 loc) • 20.8 kB
JavaScript
/* DeleteConfirmationModal.tsx generated by @compiled/babel-plugin v2.0.0 */
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DeleteConfirmationModal = void 0;
require("./DeleteConfirmationModal.compiled.css");
var _runtime = require("@compiled/react/runtime");
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _react = _interopRequireWildcard(require("react"));
var _reactIntl = require("react-intl");
var _new = _interopRequireDefault(require("@atlaskit/button/new"));
var _hooks = require("@atlaskit/editor-common/hooks");
var _messages = require("@atlaskit/editor-common/messages");
var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
var _modalDialog = _interopRequireWildcard(require("@atlaskit/modal-dialog"));
var _fg = require("@atlaskit/platform-feature-flags/fg");
var _compiled = require("@atlaskit/primitives/compiled");
var _spinner = _interopRequireDefault(require("@atlaskit/spinner"));
var _main = require("../pm-plugins/main");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t2 in e) "default" !== _t2 && {}.hasOwnProperty.call(e, _t2) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t2)) && (i.get || i.set) ? o(f, _t2, i) : f[_t2] = e[_t2]); return f; })(e, t); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var modalContentMap = {
'source-block-deleted': {
titleMultiple: _messages.syncBlockMessages.deleteConfirmationModalTitleMultiple,
titleSingle: _messages.syncBlockMessages.deletionConfirmationModalTitleSingle,
descriptionSingle: _messages.syncBlockMessages.deletionConfirmationModalDescriptionNoRef,
descriptionMultiple: _messages.syncBlockMessages.deletionConfirmationModalDescriptionNew,
confirmButtonLabel: _messages.syncBlockMessages.deleteConfirmationModalDeleteButton
},
'source-block-unpublished': {
titleMultiple: _messages.syncBlockMessages.deleteConfirmationModalTitleMultiple,
titleSingle: _messages.syncBlockMessages.deletionConfirmationModalTitleSingle,
descriptionSingle: _messages.syncBlockMessages.deletionConfirmationModalDescriptionNoRef,
descriptionMultiple: _messages.syncBlockMessages.deletionConfirmationModalDescriptionNew,
confirmButtonLabel: _messages.syncBlockMessages.deleteConfirmationModalDeleteButton
},
'source-block-unsynced': {
titleMultiple: _messages.syncBlockMessages.unsyncConfirmationModalTitle,
titleSingle: _messages.syncBlockMessages.unsyncConfirmationModalTitle,
descriptionSingle: _messages.syncBlockMessages.unsyncConfirmModalDescriptionSingle,
descriptionMultiple: _messages.syncBlockMessages.unsyncConfirmModalDescriptionMultipleNew,
confirmButtonLabel: _messages.syncBlockMessages.deleteConfirmationModalUnsyncButton
}
};
var styles = {
spinner: "_1mou1wug _195g1wug"
};
var DeleteConfirmationModal = exports.DeleteConfirmationModal = function DeleteConfirmationModal(_ref) {
var _api$core2, _api$decorations2, _api$core5, _api$core8;
var syncBlockStoreManager = _ref.syncBlockStoreManager,
api = _ref.api,
editorView = _ref.editorView;
var _useState = (0, _react.useState)(false),
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
isOpen = _useState2[0],
setIsOpen = _useState2[1];
var _useState3 = (0, _react.useState)(undefined),
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
syncBlockIds = _useState4[0],
setSyncBlockIds = _useState4[1];
var _useState5 = (0, _react.useState)(undefined),
_useState6 = (0, _slicedToArray2.default)(_useState5, 2),
referenceCount = _useState6[0],
setReferenceCount = _useState6[1];
var _useState7 = (0, _react.useState)('source-block-deleted'),
_useState8 = (0, _slicedToArray2.default)(_useState7, 2),
deleteReason = _useState8[0],
setDeleteReason = _useState8[1];
var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(api, ['connectivity', 'syncedBlock'], function (states) {
var _states$connectivityS, _states$syncedBlockSt, _states$syncedBlockSt2;
return {
mode: (_states$connectivityS = states.connectivityState) === null || _states$connectivityS === void 0 ? void 0 : _states$connectivityS.mode,
bodiedSyncBlockDeletionStatus: (_states$syncedBlockSt = states.syncedBlockState) === null || _states$syncedBlockSt === void 0 ? void 0 : _states$syncedBlockSt.bodiedSyncBlockDeletionStatus,
activeFlag: (_states$syncedBlockSt2 = states.syncedBlockState) === null || _states$syncedBlockSt2 === void 0 ? void 0 : _states$syncedBlockSt2.activeFlag
};
}),
mode = _useSharedPluginState.mode,
bodiedSyncBlockDeletionStatus = _useSharedPluginState.bodiedSyncBlockDeletionStatus,
activeFlag = _useSharedPluginState.activeFlag;
var _useIntl = (0, _reactIntl.useIntl)(),
formatMessage = _useIntl.formatMessage;
var resolverRef = _react.default.useRef(undefined);
// When a source block with no references is deleted, the modal is never shown but
// onDeleteCompleted still sets bodiedSyncBlockDeletionStatus to 'completed'. This ref signals
// the useEffect to silently reset the status without trying to close the modal (which was never open).
var skipModalOnCompletedRef = _react.default.useRef(false);
var handleClick = (0, _react.useCallback)(function (confirm) {
return function () {
var _api$core;
if (resolverRef.current) {
resolverRef.current(confirm);
resolverRef.current = undefined;
}
if (!confirm) {
setIsOpen(false);
setReferenceCount(undefined);
}
api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref2) {
var tr = _ref2.tr;
return tr.setMeta(_main.syncedBlockPluginKey, {
bodiedSyncBlockDeletionStatus: confirm ? 'processing' : 'none',
activeFlag: false
});
});
// The delete menu adds a `danger` decoration that it only clears on mouse leave, which
// never fires because clicking Delete closes the menu. It stays for the lifetime of this
// modal so the block being deleted remains highlighted, and confirming drops it along
// with the node, but cancelling has to clear it or the red highlight is left behind.
if (!confirm && editorView && (0, _fg.fg)('platform_editor_blocks_patch_7')) {
var _api$decorations;
api === null || api === void 0 || (_api$decorations = api.decorations) === null || _api$decorations === void 0 || _api$decorations.actions.removeDecoration(editorView.state, editorView.dispatch);
}
};
}, [api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions, api === null || api === void 0 || (_api$decorations2 = api.decorations) === null || _api$decorations2 === void 0 ? void 0 : _api$decorations2.actions, editorView]);
// Store activeFlag in a ref so confirmationCallback always reads the latest value
// even if it changes during the await fetchReferenceCountRef call.
var activeFlagRef = _react.default.useRef(activeFlag);
activeFlagRef.current = activeFlag;
// Use a ref so fetchReferenceCount can be called from confirmationCallback without
// being added to its dependency array — preventing confirmationCallback from being
// recreated mid-flight during an await (which would break the promise chain).
// The assignment during render is intentional — this is a standard React ref-as-latest-value
// pattern. The reduce over syncBlockIds (typically 1-2 items) is not actually expensive.
var fetchReferenceCountRef = _react.default.useRef(function () {
return Promise.resolve(0);
});
fetchReferenceCountRef.current = /*#__PURE__*/function () {
var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(syncBlockIds) {
var references;
return _regenerator.default.wrap(function (_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 1;
return Promise.all(syncBlockIds.map( /*#__PURE__*/function () {
var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(syncBlockId) {
var _result$references$le, _result$references;
var result;
return _regenerator.default.wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 1;
return syncBlockStoreManager.sourceManager.fetchReferences(syncBlockId.resourceId);
case 1:
result = _context.sent;
if (!(result !== null && result !== void 0 && result.error)) {
_context.next = 2;
break;
}
throw new Error();
case 2:
return _context.abrupt("return", (_result$references$le = (_result$references = result.references) === null || _result$references === void 0 ? void 0 : _result$references.length) !== null && _result$references$le !== void 0 ? _result$references$le : 0);
case 3:
case "end":
return _context.stop();
}
}, _callee);
}));
return function (_x2) {
return _ref4.apply(this, arguments);
};
}()));
case 1:
references = _context2.sent;
return _context2.abrupt("return", references.reduce(function (sum, count) {
return sum + count;
}, 0));
case 2:
case "end":
return _context2.stop();
}
}, _callee2);
}));
return function (_x) {
return _ref3.apply(this, arguments);
};
}();
var confirmationCallback = (0, _react.useCallback)( /*#__PURE__*/function () {
var _ref5 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(syncBlockIds, deleteReason) {
var count, _api$core3, confirmedPromise, _api$core4, _t;
return _regenerator.default.wrap(function (_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
_context3.prev = 0;
_context3.next = 1;
return fetchReferenceCountRef.current(syncBlockIds);
case 1:
count = _context3.sent;
_context3.next = 3;
break;
case 2:
_context3.prev = 2;
_t = _context3["catch"](0);
count = 1;
case 3:
if (!(count === 0)) {
_context3.next = 4;
break;
}
// No references — auto-confirm without showing the modal.
// Clear activeFlag to avoid issues with subsequent deletion attempts.
// We do NOT reset bodiedSyncBlockDeletionStatus here because onDeleteCompleted
// will set it to 'completed' after the delete call returns. Instead we use a
// ref to signal that the next 'completed' status should be silently reset
// without trying to close the modal.
skipModalOnCompletedRef.current = true;
api === null || api === void 0 || (_api$core3 = api.core) === null || _api$core3 === void 0 || _api$core3.actions.execute(function (_ref6) {
var tr = _ref6.tr;
return tr.setMeta(_main.syncedBlockPluginKey, _objectSpread({}, activeFlagRef.current ? {
activeFlag: false
} : {}));
});
return _context3.abrupt("return", true);
case 4:
setReferenceCount(count);
setIsOpen(true);
setSyncBlockIds(syncBlockIds);
if (deleteReason) {
setDeleteReason(deleteReason);
}
confirmedPromise = new Promise(function (resolve) {
resolverRef.current = resolve;
});
if (activeFlagRef.current) {
api === null || api === void 0 || (_api$core4 = api.core) === null || _api$core4 === void 0 || _api$core4.actions.execute(function (_ref7) {
var tr = _ref7.tr;
return tr.setMeta(_main.syncedBlockPluginKey, {
// Clear flag to avoid potential retry deletion of different blocks
activeFlag: false
});
});
}
return _context3.abrupt("return", confirmedPromise);
case 5:
case "end":
return _context3.stop();
}
}, _callee3, null, [[0, 2]]);
}));
return function (_x3, _x4) {
return _ref5.apply(this, arguments);
};
}(),
// fetchReferenceCountRef and activeFlagRef are intentionally omitted — they are refs
// and never change identity, ensuring confirmationCallback is never recreated mid-flight
// eslint-disable-next-line react-hooks/exhaustive-deps
[api === null || api === void 0 || (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions]);
(0, _react.useEffect)(function () {
var unregister = syncBlockStoreManager.sourceManager.registerConfirmationCallback(confirmationCallback);
return function () {
unregister();
};
}, [syncBlockStoreManager, confirmationCallback]);
(0, _react.useEffect)(function () {
if (skipModalOnCompletedRef.current) {
if (bodiedSyncBlockDeletionStatus === 'completed') {
var _api$core6;
// Deletion was auto-confirmed without showing the modal (no references).
// Reset the status to 'none' — keep skipModalOnCompletedRef true until
// we confirm the status has landed as 'none' to guard against the race where
// the next deletion opens the modal before this transaction is processed.
api === null || api === void 0 || (_api$core6 = api.core) === null || _api$core6 === void 0 || _api$core6.actions.execute(function (_ref8) {
var tr = _ref8.tr;
return tr.setMeta(_main.syncedBlockPluginKey, {
bodiedSyncBlockDeletionStatus: 'none'
});
});
} else if (bodiedSyncBlockDeletionStatus === 'none') {
skipModalOnCompletedRef.current = false;
} else if (bodiedSyncBlockDeletionStatus === 'processing' && isOpen) {
// Reset on deletion failure (e.g. network error) to avoid modal stuck in opened state.
skipModalOnCompletedRef.current = false;
}
return;
}
if (bodiedSyncBlockDeletionStatus === 'completed' && isOpen) {
var _api$core7;
// auto close modal once deletion is successful
// eslint-disable-next-line @atlassian/perf-linting/no-chain-state-updates -- Ignored via go/ees017 (to be fixed)
setIsOpen(false);
api === null || api === void 0 || (_api$core7 = api.core) === null || _api$core7 === void 0 || _api$core7.actions.execute(function (_ref9) {
var tr = _ref9.tr;
return tr.setMeta(_main.syncedBlockPluginKey, {
// Reset deletion status to have a clean state for next deletion
bodiedSyncBlockDeletionStatus: 'none'
});
});
}
}, [api === null || api === void 0 || (_api$core8 = api.core) === null || _api$core8 === void 0 ? void 0 : _api$core8.actions, bodiedSyncBlockDeletionStatus, isOpen]);
// References are fetched and set before the modal opens in
// confirmationCallback, so no additional fetch is needed here.
return /*#__PURE__*/_react.default.createElement(_modalDialog.ModalTransition, null, isOpen && /*#__PURE__*/_react.default.createElement(_modalDialog.default, {
onClose: handleClick(false),
testId: "sync-block-delete-confirmation",
height: 184
}, /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, referenceCount === undefined ? /*#__PURE__*/_react.default.createElement(_compiled.Box, {
xcss: styles.spinner
}, /*#__PURE__*/_react.default.createElement(_spinner.default, {
size: "large"
})) : /*#__PURE__*/_react.default.createElement(ModalContent, {
content: modalContentMap[deleteReason],
referenceCount: referenceCount,
handleClick: handleClick,
formatMessage: formatMessage,
isDeleting: bodiedSyncBlockDeletionStatus === 'processing',
isDisabled: (0, _editorPluginConnectivity.isOfflineMode)(mode),
deleteReason: deleteReason,
sourceCount: (syncBlockIds === null || syncBlockIds === void 0 ? void 0 : syncBlockIds.length) || 0
}))));
};
var ModalContent = function ModalContent(_ref0) {
var content = _ref0.content,
referenceCount = _ref0.referenceCount,
handleClick = _ref0.handleClick,
formatMessage = _ref0.formatMessage,
isDeleting = _ref0.isDeleting,
isDisabled = _ref0.isDisabled,
deleteReason = _ref0.deleteReason,
sourceCount = _ref0.sourceCount;
var titleMultiple = content.titleMultiple,
titleSingle = content.titleSingle,
descriptionSingle = content.descriptionSingle,
descriptionMultiple = content.descriptionMultiple,
confirmButtonLabel = content.confirmButtonLabel;
var hasNoReferenceOrFailToFetch = referenceCount === 0;
var totalLocationCount = deleteReason === 'source-block-deleted' ? referenceCount + sourceCount : referenceCount;
var isReferenceCountDescriptionEnabled = deleteReason === 'source-block-deleted' && (0, _fg.fg)('platform_editor_blocks_patch_7');
var descriptionLocationCount = isReferenceCountDescriptionEnabled ? referenceCount : totalLocationCount;
var descriptionMultipleMessage = isReferenceCountDescriptionEnabled ? _messages.syncBlockMessages.deletionConfirmationModalDescriptionReferenceCount : descriptionMultiple;
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalHeader, {
hasCloseButton: true
}, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalTitle, {
appearance: "warning"
}, hasNoReferenceOrFailToFetch ? formatMessage(titleSingle) : formatMessage(titleMultiple, {
count: totalLocationCount
}))), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalBody, null, /*#__PURE__*/_react.default.createElement(_compiled.Text, null, hasNoReferenceOrFailToFetch ? formatMessage(descriptionSingle) : formatMessage(descriptionMultipleMessage, {
syncBlockCount: descriptionLocationCount
}))), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalFooter, null, /*#__PURE__*/_react.default.createElement(_new.default, {
appearance: "subtle",
onClick: handleClick(false)
}, formatMessage(_messages.syncBlockMessages.deleteConfirmationModalCancelButton)), /*#__PURE__*/_react.default.createElement(_new.default, {
appearance: "warning",
onClick: handleClick(true),
autoFocus: true,
isDisabled: isDisabled,
isLoading: isDeleting,
testId: "synced-block-delete-confirmation-modal-".concat(deleteReason, "-button")
}, formatMessage(confirmButtonLabel))));
};