UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

362 lines (355 loc) 20.8 kB
/* 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)))); };