@atlaskit/editor-plugin-synced-block 4.1.1 → 4.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 4.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`7bb84f91500cf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7bb84f91500cf) -
8
+ [ux] EDITOR-2442 update warning modal on source sync block deletion
9
+ - Updated dependencies
10
+
11
+ ## 4.1.2
12
+
13
+ ### Patch Changes
14
+
15
+ - [`5b03ddd528034`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5b03ddd528034) -
16
+ [EDITOR-2542] Save new bodiedSyncBlock to backend when creating the node, so that the node can be
17
+ copied and reference without page being published/updated
18
+ - Updated dependencies
19
+
3
20
  ## 4.1.1
4
21
 
5
22
  ### Patch Changes
@@ -1,17 +1,13 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports.removeSyncedBlock = exports.editSyncedBlockSource = exports.createSyncedBlock = exports.copySyncedBlockReferenceToClipboard = void 0;
8
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
7
  var _analytics = require("@atlaskit/editor-common/analytics");
10
8
  var _copyButton = require("@atlaskit/editor-common/copy-button");
11
9
  var _utils = require("@atlaskit/editor-prosemirror/utils");
12
10
  var _utils2 = require("../pm-plugins/utils/utils");
13
- 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; }
14
- 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; }
15
11
  var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_ref) {
16
12
  var tr = _ref.tr,
17
13
  syncBlockStore = _ref.syncBlockStore,
@@ -22,12 +18,16 @@ var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_
22
18
 
23
19
  // If the selection is empty, we want to insert the sync block on a new line
24
20
  if (tr.selection.empty) {
25
- var storeSyncBlockNode = syncBlockStore.createBodiedSyncBlockNode();
21
+ var attrs = syncBlockStore.generateBodiedSyncBlockAttrs();
26
22
  var paragraphNode = paragraph.createAndFill({});
27
- var newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_objectSpread({}, storeSyncBlockNode.attrs), paragraphNode ? [paragraphNode] : []);
23
+ var newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(attrs, paragraphNode ? [paragraphNode] : []);
28
24
  if (!newBodiedSyncBlockNode) {
29
25
  return false;
30
26
  }
27
+
28
+ // Save the new node with empty content to backend
29
+ // This is so that the node can be copied and referenced without the source being saved/published
30
+ syncBlockStore.createBodiedSyncBlockNode(attrs);
31
31
  if (typeAheadInsert) {
32
32
  tr = typeAheadInsert(newBodiedSyncBlockNode);
33
33
  } else {
@@ -38,15 +38,21 @@ var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_
38
38
  if (!conversionInfo) {
39
39
  // TODO: EDITOR-1665 - Raise an error analytics event
40
40
  return false;
41
- } else {
42
- var _storeSyncBlockNode = syncBlockStore.createBodiedSyncBlockNode();
43
- var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_objectSpread({}, _storeSyncBlockNode.attrs), conversionInfo.contentToInclude);
44
- if (!_newBodiedSyncBlockNode) {
45
- return false;
46
- }
47
- tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
48
41
  }
42
+ var _attrs = syncBlockStore.generateBodiedSyncBlockAttrs();
43
+ var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_attrs, conversionInfo.contentToInclude);
44
+ if (!_newBodiedSyncBlockNode) {
45
+ return false;
46
+ }
47
+
48
+ // Save the new node with empty content to backend
49
+ // This is so that the node can be copied and referenced without the source being saved/published
50
+ syncBlockStore.createBodiedSyncBlockNode(_attrs);
51
+ tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
49
52
  }
53
+
54
+ // This transaction will be intercepted in filterTransaction and dispatched when saving to backend succeeds
55
+ // see filterTransaction for more details
50
56
  return tr;
51
57
  };
52
58
  var copySyncedBlockReferenceToClipboard = exports.copySyncedBlockReferenceToClipboard = function copySyncedBlockReferenceToClipboard(api) {
@@ -61,11 +61,12 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
61
61
  // or are from remote (collab) or already confirmed sync block deletion
62
62
  // We only care about local changes that change the document
63
63
  // and are not yet confirmed for sync block deletion
64
- if (!tr.docChanged || !(syncBlockStore !== null && syncBlockStore !== void 0 && syncBlockStore.requireConfirmationBeforeDelete()) || Boolean(tr.getMeta('isRemote')) || Boolean(tr.getMeta('isConfirmedSyncBlockDeletion'))) {
64
+ if (!tr.docChanged || !(syncBlockStore !== null && syncBlockStore !== void 0 && syncBlockStore.requireConfirmationBeforeDelete()) && !syncBlockStore.hasPendingCreation() || Boolean(tr.getMeta('isRemote')) || Boolean(tr.getMeta('isConfirmedSyncBlockDeletion')) || Boolean(tr.getMeta('isCommitSyncBlockCreation'))) {
65
65
  return true;
66
66
  }
67
67
  var _trackSyncBlocks = (0, _trackSyncBlocks2.trackSyncBlocks)(syncBlockStore, tr, state),
68
- removed = _trackSyncBlocks.removed;
68
+ removed = _trackSyncBlocks.removed,
69
+ added = _trackSyncBlocks.added;
69
70
  if (removed.length > 0) {
70
71
  // If there are source sync blocks being removed, and we need to confirm with user before deleting,
71
72
  // we block the transaction here, and wait for user confirmation to proceed with deletion.
@@ -74,6 +75,19 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
74
75
  syncBlockStore.deleteSyncBlocksWithConfirmation(tr, removed);
75
76
  return false;
76
77
  }
78
+ if (added.length > 0) {
79
+ // If there is bodiedSyncBlock node addition and it's waiting for the result of saving the node to backend (syncBlockStore.hasPendingCreation()),
80
+ // we need to intercept the transaction and save it in insert callback so that we only insert it to the document when backend call if backend call is successful
81
+ // The callback will be evoked by in SourceSyncBlockStoreManager.commitPendingCreation
82
+ syncBlockStore.registerCreationCallback(function () {
83
+ var _api$core;
84
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function () {
85
+ return tr.setMeta('isCommitSyncBlockCreation', true);
86
+ });
87
+ api === null || api === void 0 || api.core.actions.focus();
88
+ });
89
+ return false;
90
+ }
77
91
  return true;
78
92
  },
79
93
  appendTransaction: function appendTransaction(trs, _oldState, newState) {
@@ -8,8 +8,11 @@ Object.defineProperty(exports, "__esModule", {
8
8
  exports.DeleteConfirmationModal = void 0;
9
9
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
+ var _reactIntlNext = require("react-intl-next");
11
12
  var _new = _interopRequireDefault(require("@atlaskit/button/new"));
13
+ var _messages = require("@atlaskit/editor-common/messages");
12
14
  var _modalDialog = _interopRequireWildcard(require("@atlaskit/modal-dialog"));
15
+ var _compiled = require("@atlaskit/primitives/compiled");
13
16
  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 _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
14
17
  var DeleteConfirmationModal = exports.DeleteConfirmationModal = function DeleteConfirmationModal(_ref) {
15
18
  var syncBlockStoreManager = _ref.syncBlockStoreManager;
@@ -17,6 +20,12 @@ var DeleteConfirmationModal = exports.DeleteConfirmationModal = function DeleteC
17
20
  _useState2 = (0, _slicedToArray2.default)(_useState, 2),
18
21
  isOpen = _useState2[0],
19
22
  setIsOpen = _useState2[1];
23
+ var _useState3 = (0, _react.useState)(1),
24
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
25
+ syncBlockCount = _useState4[0],
26
+ setSyncBlockCount = _useState4[1];
27
+ var _useIntl = (0, _reactIntlNext.useIntl)(),
28
+ formatMessage = _useIntl.formatMessage;
20
29
  var resolverRef = _react.default.useRef(undefined);
21
30
  var handleClose = (0, _react.useCallback)(function (confirm) {
22
31
  return function () {
@@ -27,8 +36,9 @@ var DeleteConfirmationModal = exports.DeleteConfirmationModal = function DeleteC
27
36
  setIsOpen(false);
28
37
  };
29
38
  }, []);
30
- var confirmationCallback = (0, _react.useCallback)(function () {
39
+ var confirmationCallback = (0, _react.useCallback)(function (syncBlockCount) {
31
40
  setIsOpen(true);
41
+ setSyncBlockCount(syncBlockCount);
32
42
  var confirmedPromise = new Promise(function (resolve) {
33
43
  resolverRef.current = resolve;
34
44
  });
@@ -44,12 +54,16 @@ var DeleteConfirmationModal = exports.DeleteConfirmationModal = function DeleteC
44
54
  onClose: handleClose(false)
45
55
  }, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalHeader, {
46
56
  hasCloseButton: true
47
- }, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalTitle, null, "Confirmation")), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalBody, null, /*#__PURE__*/_react.default.createElement("div", null, "Are you sure you want to delete this synced block?")), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalFooter, null, /*#__PURE__*/_react.default.createElement(_new.default, {
57
+ }, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalTitle, {
58
+ appearance: "warning"
59
+ }, formatMessage(_messages.syncBlockMessages.deleteConfirmationModalTitle))), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalBody, null, /*#__PURE__*/_react.default.createElement(_compiled.Text, null, formatMessage(_messages.syncBlockMessages.deleteConfirmationModalDescription, {
60
+ syncBlockCount: syncBlockCount
61
+ }))), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalFooter, null, /*#__PURE__*/_react.default.createElement(_new.default, {
48
62
  appearance: "subtle",
49
63
  onClick: handleClose(false)
50
- }, "Cancel"), /*#__PURE__*/_react.default.createElement(_new.default, {
51
- appearance: "danger",
64
+ }, formatMessage(_messages.syncBlockMessages.deleteConfirmationModalCancelButton)), /*#__PURE__*/_react.default.createElement(_new.default, {
65
+ appearance: "warning",
52
66
  onClick: handleClose(true),
53
67
  autoFocus: true
54
- }, "Delete"))));
68
+ }, formatMessage(_messages.syncBlockMessages.deleteConfirmationModalDeleteButton)))));
55
69
  };
@@ -18,14 +18,16 @@ export const createSyncedBlock = ({
18
18
 
19
19
  // If the selection is empty, we want to insert the sync block on a new line
20
20
  if (tr.selection.empty) {
21
- const storeSyncBlockNode = syncBlockStore.createBodiedSyncBlockNode();
21
+ const attrs = syncBlockStore.generateBodiedSyncBlockAttrs();
22
22
  const paragraphNode = paragraph.createAndFill({});
23
- const newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill({
24
- ...storeSyncBlockNode.attrs
25
- }, paragraphNode ? [paragraphNode] : []);
23
+ const newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(attrs, paragraphNode ? [paragraphNode] : []);
26
24
  if (!newBodiedSyncBlockNode) {
27
25
  return false;
28
26
  }
27
+
28
+ // Save the new node with empty content to backend
29
+ // This is so that the node can be copied and referenced without the source being saved/published
30
+ syncBlockStore.createBodiedSyncBlockNode(attrs);
29
31
  if (typeAheadInsert) {
30
32
  tr = typeAheadInsert(newBodiedSyncBlockNode);
31
33
  } else {
@@ -36,17 +38,21 @@ export const createSyncedBlock = ({
36
38
  if (!conversionInfo) {
37
39
  // TODO: EDITOR-1665 - Raise an error analytics event
38
40
  return false;
39
- } else {
40
- const storeSyncBlockNode = syncBlockStore.createBodiedSyncBlockNode();
41
- const newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill({
42
- ...storeSyncBlockNode.attrs
43
- }, conversionInfo.contentToInclude);
44
- if (!newBodiedSyncBlockNode) {
45
- return false;
46
- }
47
- tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, newBodiedSyncBlockNode).scrollIntoView();
48
41
  }
42
+ const attrs = syncBlockStore.generateBodiedSyncBlockAttrs();
43
+ const newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(attrs, conversionInfo.contentToInclude);
44
+ if (!newBodiedSyncBlockNode) {
45
+ return false;
46
+ }
47
+
48
+ // Save the new node with empty content to backend
49
+ // This is so that the node can be copied and referenced without the source being saved/published
50
+ syncBlockStore.createBodiedSyncBlockNode(attrs);
51
+ tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, newBodiedSyncBlockNode).scrollIntoView();
49
52
  }
53
+
54
+ // This transaction will be intercepted in filterTransaction and dispatched when saving to backend succeeds
55
+ // see filterTransaction for more details
50
56
  return tr;
51
57
  };
52
58
  export const copySyncedBlockReferenceToClipboard = api => (state, _dispatch, _view) => {
@@ -53,11 +53,12 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
53
53
  // or are from remote (collab) or already confirmed sync block deletion
54
54
  // We only care about local changes that change the document
55
55
  // and are not yet confirmed for sync block deletion
56
- if (!tr.docChanged || !(syncBlockStore !== null && syncBlockStore !== void 0 && syncBlockStore.requireConfirmationBeforeDelete()) || Boolean(tr.getMeta('isRemote')) || Boolean(tr.getMeta('isConfirmedSyncBlockDeletion'))) {
56
+ if (!tr.docChanged || !(syncBlockStore !== null && syncBlockStore !== void 0 && syncBlockStore.requireConfirmationBeforeDelete()) && !syncBlockStore.hasPendingCreation() || Boolean(tr.getMeta('isRemote')) || Boolean(tr.getMeta('isConfirmedSyncBlockDeletion')) || Boolean(tr.getMeta('isCommitSyncBlockCreation'))) {
57
57
  return true;
58
58
  }
59
59
  const {
60
- removed
60
+ removed,
61
+ added
61
62
  } = trackSyncBlocks(syncBlockStore, tr, state);
62
63
  if (removed.length > 0) {
63
64
  // If there are source sync blocks being removed, and we need to confirm with user before deleting,
@@ -67,6 +68,19 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
67
68
  syncBlockStore.deleteSyncBlocksWithConfirmation(tr, removed);
68
69
  return false;
69
70
  }
71
+ if (added.length > 0) {
72
+ // If there is bodiedSyncBlock node addition and it's waiting for the result of saving the node to backend (syncBlockStore.hasPendingCreation()),
73
+ // we need to intercept the transaction and save it in insert callback so that we only insert it to the document when backend call if backend call is successful
74
+ // The callback will be evoked by in SourceSyncBlockStoreManager.commitPendingCreation
75
+ syncBlockStore.registerCreationCallback(() => {
76
+ var _api$core;
77
+ api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(() => {
78
+ return tr.setMeta('isCommitSyncBlockCreation', true);
79
+ });
80
+ api === null || api === void 0 ? void 0 : api.core.actions.focus();
81
+ });
82
+ return false;
83
+ }
70
84
  return true;
71
85
  },
72
86
  appendTransaction: (trs, _oldState, newState) => {
@@ -1,10 +1,17 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
+ import { useIntl } from 'react-intl-next';
2
3
  import Button from '@atlaskit/button/new';
4
+ import { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
3
5
  import ModalDialog, { ModalBody, ModalFooter, ModalHeader, ModalTitle, ModalTransition } from '@atlaskit/modal-dialog';
6
+ import { Text } from '@atlaskit/primitives/compiled';
4
7
  export const DeleteConfirmationModal = ({
5
8
  syncBlockStoreManager
6
9
  }) => {
7
10
  const [isOpen, setIsOpen] = useState(false);
11
+ const [syncBlockCount, setSyncBlockCount] = useState(1);
12
+ const {
13
+ formatMessage
14
+ } = useIntl();
8
15
  const resolverRef = React.useRef(undefined);
9
16
  const handleClose = useCallback(confirm => () => {
10
17
  if (resolverRef.current) {
@@ -13,8 +20,9 @@ export const DeleteConfirmationModal = ({
13
20
  }
14
21
  setIsOpen(false);
15
22
  }, []);
16
- const confirmationCallback = useCallback(() => {
23
+ const confirmationCallback = useCallback(syncBlockCount => {
17
24
  setIsOpen(true);
25
+ setSyncBlockCount(syncBlockCount);
18
26
  const confirmedPromise = new Promise(resolve => {
19
27
  resolverRef.current = resolve;
20
28
  });
@@ -30,12 +38,16 @@ export const DeleteConfirmationModal = ({
30
38
  onClose: handleClose(false)
31
39
  }, /*#__PURE__*/React.createElement(ModalHeader, {
32
40
  hasCloseButton: true
33
- }, /*#__PURE__*/React.createElement(ModalTitle, null, "Confirmation")), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement("div", null, "Are you sure you want to delete this synced block?")), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
41
+ }, /*#__PURE__*/React.createElement(ModalTitle, {
42
+ appearance: "warning"
43
+ }, formatMessage(messages.deleteConfirmationModalTitle))), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement(Text, null, formatMessage(messages.deleteConfirmationModalDescription, {
44
+ syncBlockCount
45
+ }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
34
46
  appearance: "subtle",
35
47
  onClick: handleClose(false)
36
- }, "Cancel"), /*#__PURE__*/React.createElement(Button, {
37
- appearance: "danger",
48
+ }, formatMessage(messages.deleteConfirmationModalCancelButton)), /*#__PURE__*/React.createElement(Button, {
49
+ appearance: "warning",
38
50
  onClick: handleClose(true),
39
51
  autoFocus: true
40
- }, "Delete"))));
52
+ }, formatMessage(messages.deleteConfirmationModalDeleteButton)))));
41
53
  };
@@ -1,6 +1,3 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- 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; }
3
- 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) { _defineProperty(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; }
4
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
5
2
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
6
3
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode } from '@atlaskit/editor-prosemirror/utils';
@@ -15,12 +12,16 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
15
12
 
16
13
  // If the selection is empty, we want to insert the sync block on a new line
17
14
  if (tr.selection.empty) {
18
- var storeSyncBlockNode = syncBlockStore.createBodiedSyncBlockNode();
15
+ var attrs = syncBlockStore.generateBodiedSyncBlockAttrs();
19
16
  var paragraphNode = paragraph.createAndFill({});
20
- var newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_objectSpread({}, storeSyncBlockNode.attrs), paragraphNode ? [paragraphNode] : []);
17
+ var newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(attrs, paragraphNode ? [paragraphNode] : []);
21
18
  if (!newBodiedSyncBlockNode) {
22
19
  return false;
23
20
  }
21
+
22
+ // Save the new node with empty content to backend
23
+ // This is so that the node can be copied and referenced without the source being saved/published
24
+ syncBlockStore.createBodiedSyncBlockNode(attrs);
24
25
  if (typeAheadInsert) {
25
26
  tr = typeAheadInsert(newBodiedSyncBlockNode);
26
27
  } else {
@@ -31,15 +32,21 @@ export var createSyncedBlock = function createSyncedBlock(_ref) {
31
32
  if (!conversionInfo) {
32
33
  // TODO: EDITOR-1665 - Raise an error analytics event
33
34
  return false;
34
- } else {
35
- var _storeSyncBlockNode = syncBlockStore.createBodiedSyncBlockNode();
36
- var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_objectSpread({}, _storeSyncBlockNode.attrs), conversionInfo.contentToInclude);
37
- if (!_newBodiedSyncBlockNode) {
38
- return false;
39
- }
40
- tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
41
35
  }
36
+ var _attrs = syncBlockStore.generateBodiedSyncBlockAttrs();
37
+ var _newBodiedSyncBlockNode = bodiedSyncBlock.createAndFill(_attrs, conversionInfo.contentToInclude);
38
+ if (!_newBodiedSyncBlockNode) {
39
+ return false;
40
+ }
41
+
42
+ // Save the new node with empty content to backend
43
+ // This is so that the node can be copied and referenced without the source being saved/published
44
+ syncBlockStore.createBodiedSyncBlockNode(_attrs);
45
+ tr.replaceWith(conversionInfo.from - 1, conversionInfo.to, _newBodiedSyncBlockNode).scrollIntoView();
42
46
  }
47
+
48
+ // This transaction will be intercepted in filterTransaction and dispatched when saving to backend succeeds
49
+ // see filterTransaction for more details
43
50
  return tr;
44
51
  };
45
52
  export var copySyncedBlockReferenceToClipboard = function copySyncedBlockReferenceToClipboard(api) {
@@ -55,11 +55,12 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
55
55
  // or are from remote (collab) or already confirmed sync block deletion
56
56
  // We only care about local changes that change the document
57
57
  // and are not yet confirmed for sync block deletion
58
- if (!tr.docChanged || !(syncBlockStore !== null && syncBlockStore !== void 0 && syncBlockStore.requireConfirmationBeforeDelete()) || Boolean(tr.getMeta('isRemote')) || Boolean(tr.getMeta('isConfirmedSyncBlockDeletion'))) {
58
+ if (!tr.docChanged || !(syncBlockStore !== null && syncBlockStore !== void 0 && syncBlockStore.requireConfirmationBeforeDelete()) && !syncBlockStore.hasPendingCreation() || Boolean(tr.getMeta('isRemote')) || Boolean(tr.getMeta('isConfirmedSyncBlockDeletion')) || Boolean(tr.getMeta('isCommitSyncBlockCreation'))) {
59
59
  return true;
60
60
  }
61
61
  var _trackSyncBlocks = trackSyncBlocks(syncBlockStore, tr, state),
62
- removed = _trackSyncBlocks.removed;
62
+ removed = _trackSyncBlocks.removed,
63
+ added = _trackSyncBlocks.added;
63
64
  if (removed.length > 0) {
64
65
  // If there are source sync blocks being removed, and we need to confirm with user before deleting,
65
66
  // we block the transaction here, and wait for user confirmation to proceed with deletion.
@@ -68,6 +69,19 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
68
69
  syncBlockStore.deleteSyncBlocksWithConfirmation(tr, removed);
69
70
  return false;
70
71
  }
72
+ if (added.length > 0) {
73
+ // If there is bodiedSyncBlock node addition and it's waiting for the result of saving the node to backend (syncBlockStore.hasPendingCreation()),
74
+ // we need to intercept the transaction and save it in insert callback so that we only insert it to the document when backend call if backend call is successful
75
+ // The callback will be evoked by in SourceSyncBlockStoreManager.commitPendingCreation
76
+ syncBlockStore.registerCreationCallback(function () {
77
+ var _api$core;
78
+ api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function () {
79
+ return tr.setMeta('isCommitSyncBlockCreation', true);
80
+ });
81
+ api === null || api === void 0 || api.core.actions.focus();
82
+ });
83
+ return false;
84
+ }
71
85
  return true;
72
86
  },
73
87
  appendTransaction: function appendTransaction(trs, _oldState, newState) {
@@ -1,13 +1,22 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import React, { useCallback, useEffect, useState } from 'react';
3
+ import { useIntl } from 'react-intl-next';
3
4
  import Button from '@atlaskit/button/new';
5
+ import { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
4
6
  import ModalDialog, { ModalBody, ModalFooter, ModalHeader, ModalTitle, ModalTransition } from '@atlaskit/modal-dialog';
7
+ import { Text } from '@atlaskit/primitives/compiled';
5
8
  export var DeleteConfirmationModal = function DeleteConfirmationModal(_ref) {
6
9
  var syncBlockStoreManager = _ref.syncBlockStoreManager;
7
10
  var _useState = useState(false),
8
11
  _useState2 = _slicedToArray(_useState, 2),
9
12
  isOpen = _useState2[0],
10
13
  setIsOpen = _useState2[1];
14
+ var _useState3 = useState(1),
15
+ _useState4 = _slicedToArray(_useState3, 2),
16
+ syncBlockCount = _useState4[0],
17
+ setSyncBlockCount = _useState4[1];
18
+ var _useIntl = useIntl(),
19
+ formatMessage = _useIntl.formatMessage;
11
20
  var resolverRef = React.useRef(undefined);
12
21
  var handleClose = useCallback(function (confirm) {
13
22
  return function () {
@@ -18,8 +27,9 @@ export var DeleteConfirmationModal = function DeleteConfirmationModal(_ref) {
18
27
  setIsOpen(false);
19
28
  };
20
29
  }, []);
21
- var confirmationCallback = useCallback(function () {
30
+ var confirmationCallback = useCallback(function (syncBlockCount) {
22
31
  setIsOpen(true);
32
+ setSyncBlockCount(syncBlockCount);
23
33
  var confirmedPromise = new Promise(function (resolve) {
24
34
  resolverRef.current = resolve;
25
35
  });
@@ -35,12 +45,16 @@ export var DeleteConfirmationModal = function DeleteConfirmationModal(_ref) {
35
45
  onClose: handleClose(false)
36
46
  }, /*#__PURE__*/React.createElement(ModalHeader, {
37
47
  hasCloseButton: true
38
- }, /*#__PURE__*/React.createElement(ModalTitle, null, "Confirmation")), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement("div", null, "Are you sure you want to delete this synced block?")), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
48
+ }, /*#__PURE__*/React.createElement(ModalTitle, {
49
+ appearance: "warning"
50
+ }, formatMessage(messages.deleteConfirmationModalTitle))), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement(Text, null, formatMessage(messages.deleteConfirmationModalDescription, {
51
+ syncBlockCount: syncBlockCount
52
+ }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
39
53
  appearance: "subtle",
40
54
  onClick: handleClose(false)
41
- }, "Cancel"), /*#__PURE__*/React.createElement(Button, {
42
- appearance: "danger",
55
+ }, formatMessage(messages.deleteConfirmationModalCancelButton)), /*#__PURE__*/React.createElement(Button, {
56
+ appearance: "warning",
43
57
  onClick: handleClose(true),
44
58
  autoFocus: true
45
- }, "Delete"))));
59
+ }, formatMessage(messages.deleteConfirmationModalDeleteButton)))));
46
60
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -38,7 +38,7 @@
38
38
  "@atlaskit/editor-plugin-selection": "^6.1.0",
39
39
  "@atlaskit/editor-prosemirror": "7.0.0",
40
40
  "@atlaskit/editor-shared-styles": "^3.8.0",
41
- "@atlaskit/editor-synced-block-provider": "^2.6.0",
41
+ "@atlaskit/editor-synced-block-provider": "^2.7.0",
42
42
  "@atlaskit/editor-tables": "^2.9.0",
43
43
  "@atlaskit/editor-toolbar": "^0.17.0",
44
44
  "@atlaskit/icon": "28.5.3",