@atlaskit/editor-synced-block-provider 2.15.4 → 2.15.6

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/index.js +6 -7
  3. package/dist/cjs/providers/block-service/blockServiceAPI.js +2 -1
  4. package/dist/cjs/providers/syncBlockProvider.js +16 -10
  5. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +49 -31
  6. package/dist/cjs/store-manager/sourceSyncBlockStoreManager.js +10 -3
  7. package/dist/cjs/store-manager/syncBlockStoreManager.js +9 -2
  8. package/dist/es2019/index.js +3 -2
  9. package/dist/es2019/providers/block-service/blockServiceAPI.js +3 -2
  10. package/dist/es2019/providers/syncBlockProvider.js +11 -9
  11. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +34 -16
  12. package/dist/es2019/store-manager/sourceSyncBlockStoreManager.js +10 -3
  13. package/dist/es2019/store-manager/syncBlockStoreManager.js +8 -1
  14. package/dist/esm/index.js +3 -2
  15. package/dist/esm/providers/block-service/blockServiceAPI.js +2 -1
  16. package/dist/esm/providers/syncBlockProvider.js +16 -10
  17. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +49 -31
  18. package/dist/esm/store-manager/sourceSyncBlockStoreManager.js +10 -3
  19. package/dist/esm/store-manager/syncBlockStoreManager.js +8 -1
  20. package/dist/types/index.d.ts +2 -2
  21. package/dist/types/providers/syncBlockProvider.d.ts +2 -1
  22. package/dist/types/providers/types.d.ts +1 -0
  23. package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +4 -3
  24. package/dist/types/store-manager/syncBlockStoreManager.d.ts +1 -0
  25. package/dist/types-ts4.5/index.d.ts +2 -2
  26. package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +2 -1
  27. package/dist/types-ts4.5/providers/types.d.ts +1 -0
  28. package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +4 -3
  29. package/dist/types-ts4.5/store-manager/syncBlockStoreManager.d.ts +1 -0
  30. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 2.15.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [`d40079fdeef5d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/d40079fdeef5d) -
8
+ EDITOR-4044 Fix a race condition in source sync block dirty tracking logic
9
+ - [`267f0abf6b4cf`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/267f0abf6b4cf) -
10
+ EDITOR-4044 Fix a race condition in reference sync block dirty tracking logic
11
+ - Updated dependencies
12
+
13
+ ## 2.15.5
14
+
15
+ ### Patch Changes
16
+
17
+ - [`a4ed9f55162ae`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a4ed9f55162ae) -
18
+ EDITOR-3960 Refactor Synced Blocks initialization to remove multiple re-renders and duplicated
19
+ fetches
20
+ - Updated dependencies
21
+
3
22
  ## 2.15.4
4
23
 
5
24
  ### Patch Changes
package/dist/cjs/index.js CHANGED
@@ -3,12 +3,6 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- Object.defineProperty(exports, "ReferenceSyncBlockStoreManager", {
7
- enumerable: true,
8
- get: function get() {
9
- return _referenceSyncBlockStoreManager.ReferenceSyncBlockStoreManager;
10
- }
11
- });
12
6
  Object.defineProperty(exports, "SyncBlockError", {
13
7
  enumerable: true,
14
8
  get: function get() {
@@ -171,6 +165,12 @@ Object.defineProperty(exports, "useMemoizedContentAPIProviders", {
171
165
  return _confluenceContentAPI.useMemoizedContentAPIProviders;
172
166
  }
173
167
  });
168
+ Object.defineProperty(exports, "useMemoizedSyncBlockStoreManager", {
169
+ enumerable: true,
170
+ get: function get() {
171
+ return _syncBlockStoreManager.useMemoizedSyncBlockStoreManager;
172
+ }
173
+ });
174
174
  Object.defineProperty(exports, "useMemoizedSyncedBlockProvider", {
175
175
  enumerable: true,
176
176
  get: function get() {
@@ -188,7 +188,6 @@ var _blockServiceAPI = require("./providers/block-service/blockServiceAPI");
188
188
  var _confluenceContentAPI = require("./providers/confluence/confluenceContentAPI");
189
189
  var _sourceInfo = require("./clients/confluence/sourceInfo");
190
190
  var _syncBlockProvider = require("./providers/syncBlockProvider");
191
- var _referenceSyncBlockStoreManager = require("./store-manager/referenceSyncBlockStoreManager");
192
191
  var _syncBlockStoreManager = require("./store-manager/syncBlockStoreManager");
193
192
  var _resolveSyncBlockInstance = require("./utils/resolveSyncBlockInstance");
194
193
  var _utils = require("./utils/utils");
@@ -386,7 +386,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
386
386
  blockAri: (0, _ari.generateBlockAriFromReference)(_this.sourceAri, block.resourceId),
387
387
  blockInstanceId: block.localId
388
388
  };
389
- }, noContent)
389
+ }),
390
+ noContent: noContent
390
391
  });
391
392
  case 3:
392
393
  return _context6.abrupt("return", {
@@ -32,7 +32,7 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
32
32
  * @param sourceId
33
33
  * @param nestedRendererDataProviders
34
34
  */
35
- function SyncBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions) {
35
+ function SyncBlockProvider(fetchProvider, writeProvider, sourceId) {
36
36
  var _this;
37
37
  (0, _classCallCheck2.default)(this, SyncBlockProvider);
38
38
  _this = _callSuper(this, SyncBlockProvider);
@@ -40,11 +40,16 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
40
40
  _this.fetchProvider = fetchProvider;
41
41
  _this.writeProvider = writeProvider;
42
42
  _this.sourceId = sourceId;
43
- _this.providerOptions = providerOptions;
43
+ _this.providerOptions = {};
44
44
  return _this;
45
45
  }
46
46
  (0, _inherits2.default)(SyncBlockProvider, _SyncBlockDataProvide);
47
47
  return (0, _createClass2.default)(SyncBlockProvider, [{
48
+ key: "setProviderOptions",
49
+ value: function setProviderOptions(providerOptions) {
50
+ this.providerOptions = providerOptions;
51
+ }
52
+ }, {
48
53
  key: "getProduct",
49
54
  value: function getProduct() {
50
55
  return this.writeProvider.product;
@@ -304,12 +309,13 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
304
309
  }]);
305
310
  }(_types2.SyncBlockDataProvider);
306
311
  var useMemoizedSyncedBlockProvider = exports.useMemoizedSyncedBlockProvider = function useMemoizedSyncedBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions, getSSRData) {
307
- return (0, _react.useMemo)(function () {
308
- var syncBlockProvider = new SyncBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions);
309
- var ssrData = getSSRData ? getSSRData() : undefined;
310
- if (ssrData) {
311
- syncBlockProvider.setSSRData(ssrData);
312
- }
313
- return syncBlockProvider;
314
- }, [fetchProvider, writeProvider, sourceId, providerOptions, getSSRData]);
312
+ var syncBlockProvider = (0, _react.useMemo)(function () {
313
+ return new SyncBlockProvider(fetchProvider, writeProvider, sourceId);
314
+ }, [fetchProvider, writeProvider, sourceId]);
315
+ syncBlockProvider.setProviderOptions(providerOptions);
316
+ var ssrData = getSSRData ? getSSRData() : undefined;
317
+ if (ssrData) {
318
+ syncBlockProvider.setSSRData(ssrData);
319
+ }
320
+ return syncBlockProvider;
315
321
  };
@@ -39,7 +39,8 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
39
39
  this.subscriptions = new Map();
40
40
  this.titleSubscriptions = new Map();
41
41
  this.dataProvider = dataProvider;
42
- this.syncBlockURLRequests = new Map();
42
+ this.syncBlockFetchDataRequests = new Map();
43
+ this.syncBlockSourceInfoRequests = new Map();
43
44
  this.providerFactories = new Map();
44
45
  this.fireAnalyticsEvent = fireAnalyticsEvent;
45
46
  }
@@ -160,7 +161,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
160
161
  if (!resourceId || !this.dataProvider) {
161
162
  throw new Error('Data provider or resourceId not set');
162
163
  }
163
- if (this.syncBlockURLRequests.get(resourceId)) {
164
+ if (this.syncBlockSourceInfoRequests.get(resourceId)) {
164
165
  return;
165
166
  }
166
167
  var existingSyncBlock = this.getFromCache(resourceId);
@@ -181,7 +182,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
181
182
  (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, (0, _errorHandling.getSourceInfoErrorPayload)('SourceAri, product or blockInstanceId missing'));
182
183
  return;
183
184
  }
184
- this.syncBlockURLRequests.set(resourceId, true);
185
+ this.syncBlockSourceInfoRequests.set(resourceId, true);
185
186
  this.dataProvider.fetchSyncBlockSourceInfo(blockInstanceId, sourceAri, product, this.fireAnalyticsEvent).then(function (sourceInfo) {
186
187
  if (!sourceInfo) {
187
188
  return;
@@ -194,7 +195,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
194
195
  var _this$fireAnalyticsEv3;
195
196
  (_this$fireAnalyticsEv3 = _this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 || _this$fireAnalyticsEv3.call(_this, (0, _errorHandling.getSourceInfoErrorPayload)(error.message));
196
197
  }).finally(function () {
197
- _this.syncBlockURLRequests.delete(resourceId);
198
+ _this.syncBlockSourceInfoRequests.delete(resourceId);
198
199
  });
199
200
  } catch (error) {
200
201
  var _this$fireAnalyticsEv4;
@@ -215,7 +216,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
215
216
  value: (function () {
216
217
  var _fetchSyncBlocksData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(syncBlockNodes) {
217
218
  var _this2 = this;
218
- var nodesToFetch, blocksWithNotFoundError, data, resolvedData;
219
+ var nodesToFetch, data, resolvedData;
219
220
  return _regenerator.default.wrap(function _callee2$(_context3) {
220
221
  while (1) switch (_context3.prev = _context3.next) {
221
222
  case 0:
@@ -223,7 +224,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
223
224
  _context3.next = 2;
224
225
  break;
225
226
  }
226
- return _context3.abrupt("return", Promise.resolve([]));
227
+ return _context3.abrupt("return");
227
228
  case 2:
228
229
  if (this.dataProvider) {
229
230
  _context3.next = 4;
@@ -232,18 +233,27 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
232
233
  throw new Error('Data provider not set');
233
234
  case 4:
234
235
  // Don't fetch for not_found error since the source is already deleted
235
- nodesToFetch = [], blocksWithNotFoundError = [];
236
+ nodesToFetch = [];
236
237
  syncBlockNodes.forEach(function (node) {
238
+ if (_this2.syncBlockFetchDataRequests.get(node.attrs.resourceId)) {
239
+ return;
240
+ }
237
241
  var existingSyncBlock = _this2.getFromCache(node.attrs.resourceId);
238
242
  if ((existingSyncBlock === null || existingSyncBlock === void 0 ? void 0 : existingSyncBlock.error) === _types.SyncBlockError.NotFound) {
239
- blocksWithNotFoundError.push(existingSyncBlock);
240
- } else {
241
- nodesToFetch.push(node);
243
+ return;
242
244
  }
245
+ nodesToFetch.push(node);
243
246
  });
244
- _context3.next = 8;
245
- return this.dataProvider.fetchNodesData(nodesToFetch);
246
- case 8:
247
+ nodesToFetch.forEach(function (node) {
248
+ _this2.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
249
+ });
250
+ _context3.next = 9;
251
+ return this.dataProvider.fetchNodesData(nodesToFetch).finally(function () {
252
+ nodesToFetch.forEach(function (node) {
253
+ _this2.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
254
+ });
255
+ });
256
+ case 9:
247
257
  data = _context3.sent;
248
258
  resolvedData = [];
249
259
  data.forEach(function (syncBlockInstance) {
@@ -263,7 +273,6 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
263
273
  }
264
274
  _this2.fetchSyncBlockSourceInfo(resolvedSyncBlockInstance.resourceId);
265
275
  });
266
- return _context3.abrupt("return", [].concat(resolvedData, blocksWithNotFoundError));
267
276
  case 12:
268
277
  case "end":
269
278
  return _context3.stop();
@@ -527,7 +536,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
527
536
 
528
537
  /**
529
538
  * Update reference synced blocks on the document with the BE
530
- *
539
+ *
531
540
  * @returns true if the reference synced blocks are updated successfully, false otherwise
532
541
  */
533
542
  }, {
@@ -565,42 +574,50 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
565
574
  });
566
575
  });
567
576
  if (!(blocks.length === 0)) {
568
- _context4.next = 10;
577
+ _context4.next = 11;
569
578
  break;
570
579
  }
580
+ this.isCacheDirty = false;
571
581
  return _context4.abrupt("return", true);
572
- case 10:
573
- _context4.next = 12;
582
+ case 11:
583
+ // reset isCacheDirty early to prevent race condition
584
+ // There is a race condition where if a user makes changes (create/delete) to a reference sync block
585
+ // on a live page and the reference sync block is being saved while the user
586
+ // is still making changes, the new changes might not be saved if they all happen
587
+ // exactly at a time when the updateReferenceData is being executed asynchronously.
588
+ this.isCacheDirty = false;
589
+ _context4.next = 14;
574
590
  return this.dataProvider.updateReferenceData(blocks);
575
- case 12:
591
+ case 14:
576
592
  updateResult = _context4.sent;
577
593
  if (!updateResult.success) {
578
594
  success = false;
579
595
  (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, (0, _errorHandling.updateReferenceErrorPayload)(updateResult.error || 'Failed to update reference synced blocks on the document'));
580
596
  }
581
- _context4.next = 21;
597
+ _context4.next = 23;
582
598
  break;
583
- case 16:
584
- _context4.prev = 16;
599
+ case 18:
600
+ _context4.prev = 18;
585
601
  _context4.t0 = _context4["catch"](3);
586
602
  success = false;
587
603
  (0, _monitoring.logException)(_context4.t0, {
588
604
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
589
605
  });
590
606
  (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, (0, _errorHandling.updateReferenceErrorPayload)(_context4.t0.message));
591
- case 21:
592
- _context4.prev = 21;
593
- if (success) {
594
- this.isCacheDirty = false;
607
+ case 23:
608
+ _context4.prev = 23;
609
+ if (!success) {
610
+ // set isCacheDirty back to true for cases where it failed to update the reference synced blocks on the BE
611
+ this.isCacheDirty = true;
595
612
  }
596
- return _context4.finish(21);
597
- case 24:
613
+ return _context4.finish(23);
614
+ case 26:
598
615
  return _context4.abrupt("return", success);
599
- case 25:
616
+ case 27:
600
617
  case "end":
601
618
  return _context4.stop();
602
619
  }
603
- }, _callee3, this, [[3, 16, 21, 24]]);
620
+ }, _callee3, this, [[3, 18, 23, 26]]);
604
621
  }));
605
622
  function flush() {
606
623
  return _flush.apply(this, arguments);
@@ -614,7 +631,8 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
614
631
  this.syncBlockCache.clear();
615
632
  this.subscriptions.clear();
616
633
  this.titleSubscriptions.clear();
617
- this.syncBlockURLRequests.clear();
634
+ this.syncBlockFetchDataRequests.clear();
635
+ this.syncBlockSourceInfoRequests.clear();
618
636
  this.providerFactories.clear();
619
637
  this.isRefreshingSubscriptions = false;
620
638
  this.providerFactories.forEach(function (providerFactory) {
@@ -109,6 +109,13 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
109
109
  }
110
110
  });
111
111
  bodiedSyncBlockData.push(syncBlockData);
112
+
113
+ // reset isDirty early to prevent race condition
114
+ // There is a race condition where if a user makes changes to a source sync block
115
+ // on a live page and the source sync block is being saved while the user
116
+ // is still making changes, the new changes might not be saved if they all happen
117
+ // exactly at a time when the writeNodesData is being executed asynchronously.
118
+ syncBlockData.isDirty = false;
112
119
  }
113
120
  });
114
121
  if (!(bodiedSyncBlockNodes.length === 0)) {
@@ -122,11 +129,11 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
122
129
  case 10:
123
130
  writeResults = _context.sent;
124
131
  writeResults.forEach(function (result) {
125
- // set isDirty to false on write success and unrecoverable errors like not found
126
- if (result.resourceId && (result.error === _types.SyncBlockError.NotFound || !result.error)) {
132
+ // set isDirty to true for cases where it failed to save the sync block to the BE
133
+ if (result.resourceId && result.error && result.error !== _types.SyncBlockError.NotFound) {
127
134
  var cachedData = _this2.syncBlockCache.get(result.resourceId);
128
135
  if (cachedData) {
129
- cachedData.isDirty = false;
136
+ cachedData.isDirty = true;
130
137
  }
131
138
  }
132
139
  });
@@ -4,9 +4,10 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.SyncBlockStoreManager = void 0;
7
+ exports.useMemoizedSyncBlockStoreManager = exports.SyncBlockStoreManager = void 0;
8
8
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
9
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
+ var _react = require("react");
10
11
  var _referenceSyncBlockStoreManager = require("./referenceSyncBlockStoreManager");
11
12
  var _sourceSyncBlockStoreManager = require("./sourceSyncBlockStoreManager");
12
13
  // A parent store manager responsible for the lifecycle and state management of sync blocks in an editor instance.
@@ -39,4 +40,10 @@ var SyncBlockStoreManager = exports.SyncBlockStoreManager = /*#__PURE__*/functio
39
40
  this.sourceSyncBlockStoreManager.destroy();
40
41
  }
41
42
  }]);
42
- }();
43
+ }();
44
+ var useMemoizedSyncBlockStoreManager = exports.useMemoizedSyncBlockStoreManager = function useMemoizedSyncBlockStoreManager(dataProvider, fireAnalyticsEvent) {
45
+ return (0, _react.useMemo)(function () {
46
+ var syncBlockStoreManager = new SyncBlockStoreManager(dataProvider, fireAnalyticsEvent);
47
+ return syncBlockStoreManager;
48
+ }, [dataProvider, fireAnalyticsEvent]);
49
+ };
@@ -17,9 +17,10 @@ export { useMemoizedBlockServiceAPIProviders } from './providers/block-service/b
17
17
  export { createContentAPIProvidersWithDefaultKey, useMemoizedContentAPIProviders } from './providers/confluence/confluenceContentAPI';
18
18
  export { fetchConfluencePageInfo } from './clients/confluence/sourceInfo';
19
19
  export { SyncBlockProvider as SyncedBlockProvider, useMemoizedSyncedBlockProvider } from './providers/syncBlockProvider';
20
+
20
21
  // store managers
21
- export { ReferenceSyncBlockStoreManager } from './store-manager/referenceSyncBlockStoreManager';
22
- export { SyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
22
+
23
+ export { SyncBlockStoreManager, useMemoizedSyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
23
24
 
24
25
  // utils
25
26
  export { resolveSyncBlockInstance } from './utils/resolveSyncBlockInstance';
@@ -213,7 +213,7 @@ class BlockServiceADFWriteProvider {
213
213
  } catch (error) {
214
214
  if (error instanceof BlockError) {
215
215
  if (error.status === 404) {
216
- // User should not be blocked by not_found error when deleting,
216
+ // User should not be blocked by not_found error when deleting,
217
217
  // hence returns successful result for 404 error
218
218
  return {
219
219
  resourceId,
@@ -248,7 +248,8 @@ class BlockServiceADFWriteProvider {
248
248
  blocks: blocks.map(block => ({
249
249
  blockAri: generateBlockAriFromReference(this.sourceAri, block.resourceId),
250
250
  blockInstanceId: block.localId
251
- }), noContent)
251
+ })),
252
+ noContent
252
253
  });
253
254
  return {
254
255
  success: true
@@ -15,12 +15,15 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
15
15
  * @param sourceId
16
16
  * @param nestedRendererDataProviders
17
17
  */
18
- constructor(fetchProvider, writeProvider, sourceId, providerOptions) {
18
+ constructor(fetchProvider, writeProvider, sourceId) {
19
19
  super();
20
20
  _defineProperty(this, "name", 'syncBlockProvider');
21
21
  this.fetchProvider = fetchProvider;
22
22
  this.writeProvider = writeProvider;
23
23
  this.sourceId = sourceId;
24
+ this.providerOptions = {};
25
+ }
26
+ setProviderOptions(providerOptions) {
24
27
  this.providerOptions = providerOptions;
25
28
  }
26
29
  getProduct() {
@@ -205,12 +208,11 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
205
208
  }
206
209
  }
207
210
  export const useMemoizedSyncedBlockProvider = (fetchProvider, writeProvider, sourceId, providerOptions, getSSRData) => {
208
- return useMemo(() => {
209
- const syncBlockProvider = new SyncBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions);
210
- const ssrData = getSSRData ? getSSRData() : undefined;
211
- if (ssrData) {
212
- syncBlockProvider.setSSRData(ssrData);
213
- }
214
- return syncBlockProvider;
215
- }, [fetchProvider, writeProvider, sourceId, providerOptions, getSSRData]);
211
+ const syncBlockProvider = useMemo(() => new SyncBlockProvider(fetchProvider, writeProvider, sourceId), [fetchProvider, writeProvider, sourceId]);
212
+ syncBlockProvider.setProviderOptions(providerOptions);
213
+ const ssrData = getSSRData ? getSSRData() : undefined;
214
+ if (ssrData) {
215
+ syncBlockProvider.setSSRData(ssrData);
216
+ }
217
+ return syncBlockProvider;
216
218
  };
@@ -22,7 +22,8 @@ export class ReferenceSyncBlockStoreManager {
22
22
  this.subscriptions = new Map();
23
23
  this.titleSubscriptions = new Map();
24
24
  this.dataProvider = dataProvider;
25
- this.syncBlockURLRequests = new Map();
25
+ this.syncBlockFetchDataRequests = new Map();
26
+ this.syncBlockSourceInfoRequests = new Map();
26
27
  this.providerFactories = new Map();
27
28
  this.fireAnalyticsEvent = fireAnalyticsEvent;
28
29
  }
@@ -76,7 +77,7 @@ export class ReferenceSyncBlockStoreManager {
76
77
  if (!resourceId || !this.dataProvider) {
77
78
  throw new Error('Data provider or resourceId not set');
78
79
  }
79
- if (this.syncBlockURLRequests.get(resourceId)) {
80
+ if (this.syncBlockSourceInfoRequests.get(resourceId)) {
80
81
  return;
81
82
  }
82
83
  const existingSyncBlock = this.getFromCache(resourceId);
@@ -98,7 +99,7 @@ export class ReferenceSyncBlockStoreManager {
98
99
  (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 ? void 0 : _this$fireAnalyticsEv2.call(this, getSourceInfoErrorPayload('SourceAri, product or blockInstanceId missing'));
99
100
  return;
100
101
  }
101
- this.syncBlockURLRequests.set(resourceId, true);
102
+ this.syncBlockSourceInfoRequests.set(resourceId, true);
102
103
  this.dataProvider.fetchSyncBlockSourceInfo(blockInstanceId, sourceAri, product, this.fireAnalyticsEvent).then(sourceInfo => {
103
104
  if (!sourceInfo) {
104
105
  return;
@@ -111,7 +112,7 @@ export class ReferenceSyncBlockStoreManager {
111
112
  var _this$fireAnalyticsEv3;
112
113
  (_this$fireAnalyticsEv3 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 ? void 0 : _this$fireAnalyticsEv3.call(this, getSourceInfoErrorPayload(error.message));
113
114
  }).finally(() => {
114
- this.syncBlockURLRequests.delete(resourceId);
115
+ this.syncBlockSourceInfoRequests.delete(resourceId);
115
116
  });
116
117
  } catch (error) {
117
118
  var _this$fireAnalyticsEv4;
@@ -129,24 +130,32 @@ export class ReferenceSyncBlockStoreManager {
129
130
  */
130
131
  async fetchSyncBlocksData(syncBlockNodes) {
131
132
  if (syncBlockNodes.length === 0) {
132
- return Promise.resolve([]);
133
+ return;
133
134
  }
134
135
  if (!this.dataProvider) {
135
136
  throw new Error('Data provider not set');
136
137
  }
137
138
 
138
139
  // Don't fetch for not_found error since the source is already deleted
139
- const nodesToFetch = [],
140
- blocksWithNotFoundError = [];
140
+ const nodesToFetch = [];
141
141
  syncBlockNodes.forEach(node => {
142
+ if (this.syncBlockFetchDataRequests.get(node.attrs.resourceId)) {
143
+ return;
144
+ }
142
145
  const existingSyncBlock = this.getFromCache(node.attrs.resourceId);
143
146
  if ((existingSyncBlock === null || existingSyncBlock === void 0 ? void 0 : existingSyncBlock.error) === SyncBlockError.NotFound) {
144
- blocksWithNotFoundError.push(existingSyncBlock);
145
- } else {
146
- nodesToFetch.push(node);
147
+ return;
147
148
  }
149
+ nodesToFetch.push(node);
150
+ });
151
+ nodesToFetch.forEach(node => {
152
+ this.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
153
+ });
154
+ const data = await this.dataProvider.fetchNodesData(nodesToFetch).finally(() => {
155
+ nodesToFetch.forEach(node => {
156
+ this.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
157
+ });
148
158
  });
149
- const data = await this.dataProvider.fetchNodesData(nodesToFetch);
150
159
  const resolvedData = [];
151
160
  data.forEach(syncBlockInstance => {
152
161
  if (!syncBlockInstance.resourceId) {
@@ -165,7 +174,6 @@ export class ReferenceSyncBlockStoreManager {
165
174
  }
166
175
  this.fetchSyncBlockSourceInfo(resolvedSyncBlockInstance.resourceId);
167
176
  });
168
- return [...resolvedData, ...blocksWithNotFoundError];
169
177
  }
170
178
  updateCacheWithSourceInfo(resourceId, sourceInfo) {
171
179
  const existingSyncBlock = this.getFromCache(resourceId);
@@ -409,7 +417,7 @@ export class ReferenceSyncBlockStoreManager {
409
417
 
410
418
  /**
411
419
  * Update reference synced blocks on the document with the BE
412
- *
420
+ *
413
421
  * @returns true if the reference synced blocks are updated successfully, false otherwise
414
422
  */
415
423
  async flush() {
@@ -433,8 +441,16 @@ export class ReferenceSyncBlockStoreManager {
433
441
  });
434
442
  });
435
443
  if (blocks.length === 0) {
444
+ this.isCacheDirty = false;
436
445
  return true;
437
446
  }
447
+
448
+ // reset isCacheDirty early to prevent race condition
449
+ // There is a race condition where if a user makes changes (create/delete) to a reference sync block
450
+ // on a live page and the reference sync block is being saved while the user
451
+ // is still making changes, the new changes might not be saved if they all happen
452
+ // exactly at a time when the updateReferenceData is being executed asynchronously.
453
+ this.isCacheDirty = false;
438
454
  const updateResult = await this.dataProvider.updateReferenceData(blocks);
439
455
  if (!updateResult.success) {
440
456
  var _this$fireAnalyticsEv10;
@@ -449,8 +465,9 @@ export class ReferenceSyncBlockStoreManager {
449
465
  });
450
466
  (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, updateReferenceErrorPayload(error.message));
451
467
  } finally {
452
- if (success) {
453
- this.isCacheDirty = false;
468
+ if (!success) {
469
+ // set isCacheDirty back to true for cases where it failed to update the reference synced blocks on the BE
470
+ this.isCacheDirty = true;
454
471
  }
455
472
  }
456
473
  return success;
@@ -460,7 +477,8 @@ export class ReferenceSyncBlockStoreManager {
460
477
  this.syncBlockCache.clear();
461
478
  this.subscriptions.clear();
462
479
  this.titleSubscriptions.clear();
463
- this.syncBlockURLRequests.clear();
480
+ this.syncBlockFetchDataRequests.clear();
481
+ this.syncBlockSourceInfoRequests.clear();
464
482
  this.providerFactories.clear();
465
483
  this.isRefreshingSubscriptions = false;
466
484
  this.providerFactories.forEach(providerFactory => {
@@ -82,6 +82,13 @@ export class SourceSyncBlockStoreManager {
82
82
  }
83
83
  });
84
84
  bodiedSyncBlockData.push(syncBlockData);
85
+
86
+ // reset isDirty early to prevent race condition
87
+ // There is a race condition where if a user makes changes to a source sync block
88
+ // on a live page and the source sync block is being saved while the user
89
+ // is still making changes, the new changes might not be saved if they all happen
90
+ // exactly at a time when the writeNodesData is being executed asynchronously.
91
+ syncBlockData.isDirty = false;
85
92
  }
86
93
  });
87
94
  if (bodiedSyncBlockNodes.length === 0) {
@@ -89,11 +96,11 @@ export class SourceSyncBlockStoreManager {
89
96
  }
90
97
  const writeResults = await this.dataProvider.writeNodesData(bodiedSyncBlockNodes, bodiedSyncBlockData);
91
98
  writeResults.forEach(result => {
92
- // set isDirty to false on write success and unrecoverable errors like not found
93
- if (result.resourceId && (result.error === SyncBlockError.NotFound || !result.error)) {
99
+ // set isDirty to true for cases where it failed to save the sync block to the BE
100
+ if (result.resourceId && result.error && result.error !== SyncBlockError.NotFound) {
94
101
  const cachedData = this.syncBlockCache.get(result.resourceId);
95
102
  if (cachedData) {
96
- cachedData.isDirty = false;
103
+ cachedData.isDirty = true;
97
104
  }
98
105
  }
99
106
  });
@@ -1,3 +1,4 @@
1
+ import { useMemo } from 'react';
1
2
  import { ReferenceSyncBlockStoreManager } from './referenceSyncBlockStoreManager';
2
3
  import { SourceSyncBlockStoreManager } from './sourceSyncBlockStoreManager';
3
4
 
@@ -23,4 +24,10 @@ export class SyncBlockStoreManager {
23
24
  this.referenceSyncBlockStoreManager.destroy();
24
25
  this.sourceSyncBlockStoreManager.destroy();
25
26
  }
26
- }
27
+ }
28
+ export const useMemoizedSyncBlockStoreManager = (dataProvider, fireAnalyticsEvent) => {
29
+ return useMemo(() => {
30
+ const syncBlockStoreManager = new SyncBlockStoreManager(dataProvider, fireAnalyticsEvent);
31
+ return syncBlockStoreManager;
32
+ }, [dataProvider, fireAnalyticsEvent]);
33
+ };
package/dist/esm/index.js CHANGED
@@ -17,9 +17,10 @@ export { useMemoizedBlockServiceAPIProviders } from './providers/block-service/b
17
17
  export { createContentAPIProvidersWithDefaultKey, useMemoizedContentAPIProviders } from './providers/confluence/confluenceContentAPI';
18
18
  export { fetchConfluencePageInfo } from './clients/confluence/sourceInfo';
19
19
  export { SyncBlockProvider as SyncedBlockProvider, useMemoizedSyncedBlockProvider } from './providers/syncBlockProvider';
20
+
20
21
  // store managers
21
- export { ReferenceSyncBlockStoreManager } from './store-manager/referenceSyncBlockStoreManager';
22
- export { SyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
22
+
23
+ export { SyncBlockStoreManager, useMemoizedSyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
23
24
 
24
25
  // utils
25
26
  export { resolveSyncBlockInstance } from './utils/resolveSyncBlockInstance';
@@ -378,7 +378,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
378
378
  blockAri: generateBlockAriFromReference(_this.sourceAri, block.resourceId),
379
379
  blockInstanceId: block.localId
380
380
  };
381
- }, noContent)
381
+ }),
382
+ noContent: noContent
382
383
  });
383
384
  case 3:
384
385
  return _context6.abrupt("return", {
@@ -25,7 +25,7 @@ export var SyncBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
25
25
  * @param sourceId
26
26
  * @param nestedRendererDataProviders
27
27
  */
28
- function SyncBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions) {
28
+ function SyncBlockProvider(fetchProvider, writeProvider, sourceId) {
29
29
  var _this;
30
30
  _classCallCheck(this, SyncBlockProvider);
31
31
  _this = _callSuper(this, SyncBlockProvider);
@@ -33,11 +33,16 @@ export var SyncBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
33
33
  _this.fetchProvider = fetchProvider;
34
34
  _this.writeProvider = writeProvider;
35
35
  _this.sourceId = sourceId;
36
- _this.providerOptions = providerOptions;
36
+ _this.providerOptions = {};
37
37
  return _this;
38
38
  }
39
39
  _inherits(SyncBlockProvider, _SyncBlockDataProvide);
40
40
  return _createClass(SyncBlockProvider, [{
41
+ key: "setProviderOptions",
42
+ value: function setProviderOptions(providerOptions) {
43
+ this.providerOptions = providerOptions;
44
+ }
45
+ }, {
41
46
  key: "getProduct",
42
47
  value: function getProduct() {
43
48
  return this.writeProvider.product;
@@ -297,12 +302,13 @@ export var SyncBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
297
302
  }]);
298
303
  }(SyncBlockDataProvider);
299
304
  export var useMemoizedSyncedBlockProvider = function useMemoizedSyncedBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions, getSSRData) {
300
- return useMemo(function () {
301
- var syncBlockProvider = new SyncBlockProvider(fetchProvider, writeProvider, sourceId, providerOptions);
302
- var ssrData = getSSRData ? getSSRData() : undefined;
303
- if (ssrData) {
304
- syncBlockProvider.setSSRData(ssrData);
305
- }
306
- return syncBlockProvider;
307
- }, [fetchProvider, writeProvider, sourceId, providerOptions, getSSRData]);
305
+ var syncBlockProvider = useMemo(function () {
306
+ return new SyncBlockProvider(fetchProvider, writeProvider, sourceId);
307
+ }, [fetchProvider, writeProvider, sourceId]);
308
+ syncBlockProvider.setProviderOptions(providerOptions);
309
+ var ssrData = getSSRData ? getSSRData() : undefined;
310
+ if (ssrData) {
311
+ syncBlockProvider.setSSRData(ssrData);
312
+ }
313
+ return syncBlockProvider;
308
314
  };
@@ -33,7 +33,8 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
33
33
  this.subscriptions = new Map();
34
34
  this.titleSubscriptions = new Map();
35
35
  this.dataProvider = dataProvider;
36
- this.syncBlockURLRequests = new Map();
36
+ this.syncBlockFetchDataRequests = new Map();
37
+ this.syncBlockSourceInfoRequests = new Map();
37
38
  this.providerFactories = new Map();
38
39
  this.fireAnalyticsEvent = fireAnalyticsEvent;
39
40
  }
@@ -154,7 +155,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
154
155
  if (!resourceId || !this.dataProvider) {
155
156
  throw new Error('Data provider or resourceId not set');
156
157
  }
157
- if (this.syncBlockURLRequests.get(resourceId)) {
158
+ if (this.syncBlockSourceInfoRequests.get(resourceId)) {
158
159
  return;
159
160
  }
160
161
  var existingSyncBlock = this.getFromCache(resourceId);
@@ -175,7 +176,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
175
176
  (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, getSourceInfoErrorPayload('SourceAri, product or blockInstanceId missing'));
176
177
  return;
177
178
  }
178
- this.syncBlockURLRequests.set(resourceId, true);
179
+ this.syncBlockSourceInfoRequests.set(resourceId, true);
179
180
  this.dataProvider.fetchSyncBlockSourceInfo(blockInstanceId, sourceAri, product, this.fireAnalyticsEvent).then(function (sourceInfo) {
180
181
  if (!sourceInfo) {
181
182
  return;
@@ -188,7 +189,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
188
189
  var _this$fireAnalyticsEv3;
189
190
  (_this$fireAnalyticsEv3 = _this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 || _this$fireAnalyticsEv3.call(_this, getSourceInfoErrorPayload(error.message));
190
191
  }).finally(function () {
191
- _this.syncBlockURLRequests.delete(resourceId);
192
+ _this.syncBlockSourceInfoRequests.delete(resourceId);
192
193
  });
193
194
  } catch (error) {
194
195
  var _this$fireAnalyticsEv4;
@@ -209,7 +210,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
209
210
  value: (function () {
210
211
  var _fetchSyncBlocksData = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syncBlockNodes) {
211
212
  var _this2 = this;
212
- var nodesToFetch, blocksWithNotFoundError, data, resolvedData;
213
+ var nodesToFetch, data, resolvedData;
213
214
  return _regeneratorRuntime.wrap(function _callee2$(_context3) {
214
215
  while (1) switch (_context3.prev = _context3.next) {
215
216
  case 0:
@@ -217,7 +218,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
217
218
  _context3.next = 2;
218
219
  break;
219
220
  }
220
- return _context3.abrupt("return", Promise.resolve([]));
221
+ return _context3.abrupt("return");
221
222
  case 2:
222
223
  if (this.dataProvider) {
223
224
  _context3.next = 4;
@@ -226,18 +227,27 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
226
227
  throw new Error('Data provider not set');
227
228
  case 4:
228
229
  // Don't fetch for not_found error since the source is already deleted
229
- nodesToFetch = [], blocksWithNotFoundError = [];
230
+ nodesToFetch = [];
230
231
  syncBlockNodes.forEach(function (node) {
232
+ if (_this2.syncBlockFetchDataRequests.get(node.attrs.resourceId)) {
233
+ return;
234
+ }
231
235
  var existingSyncBlock = _this2.getFromCache(node.attrs.resourceId);
232
236
  if ((existingSyncBlock === null || existingSyncBlock === void 0 ? void 0 : existingSyncBlock.error) === SyncBlockError.NotFound) {
233
- blocksWithNotFoundError.push(existingSyncBlock);
234
- } else {
235
- nodesToFetch.push(node);
237
+ return;
236
238
  }
239
+ nodesToFetch.push(node);
237
240
  });
238
- _context3.next = 8;
239
- return this.dataProvider.fetchNodesData(nodesToFetch);
240
- case 8:
241
+ nodesToFetch.forEach(function (node) {
242
+ _this2.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
243
+ });
244
+ _context3.next = 9;
245
+ return this.dataProvider.fetchNodesData(nodesToFetch).finally(function () {
246
+ nodesToFetch.forEach(function (node) {
247
+ _this2.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
248
+ });
249
+ });
250
+ case 9:
241
251
  data = _context3.sent;
242
252
  resolvedData = [];
243
253
  data.forEach(function (syncBlockInstance) {
@@ -257,7 +267,6 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
257
267
  }
258
268
  _this2.fetchSyncBlockSourceInfo(resolvedSyncBlockInstance.resourceId);
259
269
  });
260
- return _context3.abrupt("return", [].concat(resolvedData, blocksWithNotFoundError));
261
270
  case 12:
262
271
  case "end":
263
272
  return _context3.stop();
@@ -521,7 +530,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
521
530
 
522
531
  /**
523
532
  * Update reference synced blocks on the document with the BE
524
- *
533
+ *
525
534
  * @returns true if the reference synced blocks are updated successfully, false otherwise
526
535
  */
527
536
  }, {
@@ -559,42 +568,50 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
559
568
  });
560
569
  });
561
570
  if (!(blocks.length === 0)) {
562
- _context4.next = 10;
571
+ _context4.next = 11;
563
572
  break;
564
573
  }
574
+ this.isCacheDirty = false;
565
575
  return _context4.abrupt("return", true);
566
- case 10:
567
- _context4.next = 12;
576
+ case 11:
577
+ // reset isCacheDirty early to prevent race condition
578
+ // There is a race condition where if a user makes changes (create/delete) to a reference sync block
579
+ // on a live page and the reference sync block is being saved while the user
580
+ // is still making changes, the new changes might not be saved if they all happen
581
+ // exactly at a time when the updateReferenceData is being executed asynchronously.
582
+ this.isCacheDirty = false;
583
+ _context4.next = 14;
568
584
  return this.dataProvider.updateReferenceData(blocks);
569
- case 12:
585
+ case 14:
570
586
  updateResult = _context4.sent;
571
587
  if (!updateResult.success) {
572
588
  success = false;
573
589
  (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document'));
574
590
  }
575
- _context4.next = 21;
591
+ _context4.next = 23;
576
592
  break;
577
- case 16:
578
- _context4.prev = 16;
593
+ case 18:
594
+ _context4.prev = 18;
579
595
  _context4.t0 = _context4["catch"](3);
580
596
  success = false;
581
597
  logException(_context4.t0, {
582
598
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
583
599
  });
584
600
  (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, updateReferenceErrorPayload(_context4.t0.message));
585
- case 21:
586
- _context4.prev = 21;
587
- if (success) {
588
- this.isCacheDirty = false;
601
+ case 23:
602
+ _context4.prev = 23;
603
+ if (!success) {
604
+ // set isCacheDirty back to true for cases where it failed to update the reference synced blocks on the BE
605
+ this.isCacheDirty = true;
589
606
  }
590
- return _context4.finish(21);
591
- case 24:
607
+ return _context4.finish(23);
608
+ case 26:
592
609
  return _context4.abrupt("return", success);
593
- case 25:
610
+ case 27:
594
611
  case "end":
595
612
  return _context4.stop();
596
613
  }
597
- }, _callee3, this, [[3, 16, 21, 24]]);
614
+ }, _callee3, this, [[3, 18, 23, 26]]);
598
615
  }));
599
616
  function flush() {
600
617
  return _flush.apply(this, arguments);
@@ -608,7 +625,8 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
608
625
  this.syncBlockCache.clear();
609
626
  this.subscriptions.clear();
610
627
  this.titleSubscriptions.clear();
611
- this.syncBlockURLRequests.clear();
628
+ this.syncBlockFetchDataRequests.clear();
629
+ this.syncBlockSourceInfoRequests.clear();
612
630
  this.providerFactories.clear();
613
631
  this.isRefreshingSubscriptions = false;
614
632
  this.providerFactories.forEach(function (providerFactory) {
@@ -103,6 +103,13 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
103
103
  }
104
104
  });
105
105
  bodiedSyncBlockData.push(syncBlockData);
106
+
107
+ // reset isDirty early to prevent race condition
108
+ // There is a race condition where if a user makes changes to a source sync block
109
+ // on a live page and the source sync block is being saved while the user
110
+ // is still making changes, the new changes might not be saved if they all happen
111
+ // exactly at a time when the writeNodesData is being executed asynchronously.
112
+ syncBlockData.isDirty = false;
106
113
  }
107
114
  });
108
115
  if (!(bodiedSyncBlockNodes.length === 0)) {
@@ -116,11 +123,11 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
116
123
  case 10:
117
124
  writeResults = _context.sent;
118
125
  writeResults.forEach(function (result) {
119
- // set isDirty to false on write success and unrecoverable errors like not found
120
- if (result.resourceId && (result.error === SyncBlockError.NotFound || !result.error)) {
126
+ // set isDirty to true for cases where it failed to save the sync block to the BE
127
+ if (result.resourceId && result.error && result.error !== SyncBlockError.NotFound) {
121
128
  var cachedData = _this2.syncBlockCache.get(result.resourceId);
122
129
  if (cachedData) {
123
- cachedData.isDirty = false;
130
+ cachedData.isDirty = true;
124
131
  }
125
132
  }
126
133
  });
@@ -1,5 +1,6 @@
1
1
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
2
  import _createClass from "@babel/runtime/helpers/createClass";
3
+ import { useMemo } from 'react';
3
4
  import { ReferenceSyncBlockStoreManager } from './referenceSyncBlockStoreManager';
4
5
  import { SourceSyncBlockStoreManager } from './sourceSyncBlockStoreManager';
5
6
 
@@ -33,4 +34,10 @@ export var SyncBlockStoreManager = /*#__PURE__*/function () {
33
34
  this.sourceSyncBlockStoreManager.destroy();
34
35
  }
35
36
  }]);
36
- }();
37
+ }();
38
+ export var useMemoizedSyncBlockStoreManager = function useMemoizedSyncBlockStoreManager(dataProvider, fireAnalyticsEvent) {
39
+ return useMemo(function () {
40
+ var syncBlockStoreManager = new SyncBlockStoreManager(dataProvider, fireAnalyticsEvent);
41
+ return syncBlockStoreManager;
42
+ }, [dataProvider, fireAnalyticsEvent]);
43
+ };
@@ -11,8 +11,8 @@ export { createContentAPIProvidersWithDefaultKey, useMemoizedContentAPIProviders
11
11
  export { fetchConfluencePageInfo } from './clients/confluence/sourceInfo';
12
12
  export { SyncBlockProvider as SyncedBlockProvider, useMemoizedSyncedBlockProvider, } from './providers/syncBlockProvider';
13
13
  export type { ADFFetchProvider, ADFWriteProvider, SyncBlockDataProvider, SyncBlockInstance, MediaEmojiProviderOptions, SyncedBlockRendererProviderOptions, SyncBlockRendererProviderCreator, } from './providers/types';
14
- export { ReferenceSyncBlockStoreManager } from './store-manager/referenceSyncBlockStoreManager';
15
- export { SyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
14
+ export { type ReferenceSyncBlockStoreManager } from './store-manager/referenceSyncBlockStoreManager';
15
+ export { SyncBlockStoreManager, useMemoizedSyncBlockStoreManager, } from './store-manager/syncBlockStoreManager';
16
16
  export { resolveSyncBlockInstance } from './utils/resolveSyncBlockInstance';
17
17
  export { createSyncBlockNode, convertSyncBlockPMNodeToSyncBlockData, convertSyncBlockJSONNodeToSyncBlockNode, convertPMNodesToSyncBlockNodes, getContentIdAndProductFromResourceId } from './utils/utils';
18
18
  export { fetchErrorPayload } from './utils/errorHandling';
@@ -16,7 +16,8 @@ export declare class SyncBlockProvider extends SyncBlockDataProvider {
16
16
  * @param sourceId
17
17
  * @param nestedRendererDataProviders
18
18
  */
19
- constructor(fetchProvider: ADFFetchProvider, writeProvider: ADFWriteProvider, sourceId: string, providerOptions: SyncedBlockRendererProviderOptions);
19
+ constructor(fetchProvider: ADFFetchProvider, writeProvider: ADFWriteProvider, sourceId: string);
20
+ setProviderOptions(providerOptions: SyncedBlockRendererProviderOptions): void;
20
21
  getProduct(): SyncBlockProduct | undefined;
21
22
  /**
22
23
  * Check if the node is supported by the provider
@@ -85,6 +85,7 @@ export declare abstract class SyncBlockDataProvider extends NodeDataProvider<Syn
85
85
  abstract getSourceId(): ResourceId;
86
86
  abstract getProduct(): SyncBlockProduct | undefined;
87
87
  abstract fetchSyncBlockSourceInfo(localId: BlockInstanceId, sourceAri: string, sourceProduct: SyncBlockProduct, fireAnalyticsEvent?: (payload: RendererSyncBlockEventPayload) => void): Promise<SyncBlockSourceInfo | undefined>;
88
+ abstract setProviderOptions(providerOptions: SyncedBlockRendererProviderOptions): void;
88
89
  abstract getSyncedBlockRendererProviderOptions(): SyncedBlockRendererProviderOptions;
89
90
  abstract retrieveSyncBlockParentInfo(sourceAri: string, sourceProduct: SyncBlockProduct): SyncBlockParentInfo | undefined;
90
91
  /**
@@ -11,7 +11,8 @@ export declare class ReferenceSyncBlockStoreManager {
11
11
  private titleSubscriptions;
12
12
  private providerFactories;
13
13
  private fireAnalyticsEvent?;
14
- private syncBlockURLRequests;
14
+ private syncBlockFetchDataRequests;
15
+ private syncBlockSourceInfoRequests;
15
16
  private isRefreshingSubscriptions;
16
17
  constructor(dataProvider?: SyncBlockDataProvider, fireAnalyticsEvent?: (payload: RendererSyncBlockEventPayload) => void);
17
18
  generateResourceIdForReference(sourceId: ResourceId): ResourceId;
@@ -28,11 +29,11 @@ export declare class ReferenceSyncBlockStoreManager {
28
29
  * @param syncBlockNodes - The array of sync block nodes to fetch data for
29
30
  * @returns The fetched sync block data results
30
31
  */
31
- fetchSyncBlocksData(syncBlockNodes: SyncBlockNode[]): Promise<SyncBlockInstance[]>;
32
+ fetchSyncBlocksData(syncBlockNodes: SyncBlockNode[]): Promise<void>;
32
33
  private updateCacheWithSourceInfo;
33
34
  private updateCache;
34
35
  private updateSourceTitleSubscriptions;
35
- private getFromCache;
36
+ getFromCache(resourceId: ResourceId): SyncBlockInstance | undefined;
36
37
  private deleteFromCache;
37
38
  subscribeToSyncBlock(resourceId: string, localId: string, callback: SubscriptionCallback): () => void;
38
39
  subscribeToSourceTitle(node: PMNode, callback: TitleSubscriptionCallback): () => void;
@@ -10,3 +10,4 @@ export declare class SyncBlockStoreManager {
10
10
  get sourceManager(): SourceSyncBlockStoreManager;
11
11
  destroy(): void;
12
12
  }
13
+ export declare const useMemoizedSyncBlockStoreManager: (dataProvider?: SyncBlockDataProvider, fireAnalyticsEvent?: (payload: SyncBlockEventPayload) => void) => SyncBlockStoreManager;
@@ -11,8 +11,8 @@ export { createContentAPIProvidersWithDefaultKey, useMemoizedContentAPIProviders
11
11
  export { fetchConfluencePageInfo } from './clients/confluence/sourceInfo';
12
12
  export { SyncBlockProvider as SyncedBlockProvider, useMemoizedSyncedBlockProvider, } from './providers/syncBlockProvider';
13
13
  export type { ADFFetchProvider, ADFWriteProvider, SyncBlockDataProvider, SyncBlockInstance, MediaEmojiProviderOptions, SyncedBlockRendererProviderOptions, SyncBlockRendererProviderCreator, } from './providers/types';
14
- export { ReferenceSyncBlockStoreManager } from './store-manager/referenceSyncBlockStoreManager';
15
- export { SyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
14
+ export { type ReferenceSyncBlockStoreManager } from './store-manager/referenceSyncBlockStoreManager';
15
+ export { SyncBlockStoreManager, useMemoizedSyncBlockStoreManager, } from './store-manager/syncBlockStoreManager';
16
16
  export { resolveSyncBlockInstance } from './utils/resolveSyncBlockInstance';
17
17
  export { createSyncBlockNode, convertSyncBlockPMNodeToSyncBlockData, convertSyncBlockJSONNodeToSyncBlockNode, convertPMNodesToSyncBlockNodes, getContentIdAndProductFromResourceId } from './utils/utils';
18
18
  export { fetchErrorPayload } from './utils/errorHandling';
@@ -16,7 +16,8 @@ export declare class SyncBlockProvider extends SyncBlockDataProvider {
16
16
  * @param sourceId
17
17
  * @param nestedRendererDataProviders
18
18
  */
19
- constructor(fetchProvider: ADFFetchProvider, writeProvider: ADFWriteProvider, sourceId: string, providerOptions: SyncedBlockRendererProviderOptions);
19
+ constructor(fetchProvider: ADFFetchProvider, writeProvider: ADFWriteProvider, sourceId: string);
20
+ setProviderOptions(providerOptions: SyncedBlockRendererProviderOptions): void;
20
21
  getProduct(): SyncBlockProduct | undefined;
21
22
  /**
22
23
  * Check if the node is supported by the provider
@@ -85,6 +85,7 @@ export declare abstract class SyncBlockDataProvider extends NodeDataProvider<Syn
85
85
  abstract getSourceId(): ResourceId;
86
86
  abstract getProduct(): SyncBlockProduct | undefined;
87
87
  abstract fetchSyncBlockSourceInfo(localId: BlockInstanceId, sourceAri: string, sourceProduct: SyncBlockProduct, fireAnalyticsEvent?: (payload: RendererSyncBlockEventPayload) => void): Promise<SyncBlockSourceInfo | undefined>;
88
+ abstract setProviderOptions(providerOptions: SyncedBlockRendererProviderOptions): void;
88
89
  abstract getSyncedBlockRendererProviderOptions(): SyncedBlockRendererProviderOptions;
89
90
  abstract retrieveSyncBlockParentInfo(sourceAri: string, sourceProduct: SyncBlockProduct): SyncBlockParentInfo | undefined;
90
91
  /**
@@ -11,7 +11,8 @@ export declare class ReferenceSyncBlockStoreManager {
11
11
  private titleSubscriptions;
12
12
  private providerFactories;
13
13
  private fireAnalyticsEvent?;
14
- private syncBlockURLRequests;
14
+ private syncBlockFetchDataRequests;
15
+ private syncBlockSourceInfoRequests;
15
16
  private isRefreshingSubscriptions;
16
17
  constructor(dataProvider?: SyncBlockDataProvider, fireAnalyticsEvent?: (payload: RendererSyncBlockEventPayload) => void);
17
18
  generateResourceIdForReference(sourceId: ResourceId): ResourceId;
@@ -28,11 +29,11 @@ export declare class ReferenceSyncBlockStoreManager {
28
29
  * @param syncBlockNodes - The array of sync block nodes to fetch data for
29
30
  * @returns The fetched sync block data results
30
31
  */
31
- fetchSyncBlocksData(syncBlockNodes: SyncBlockNode[]): Promise<SyncBlockInstance[]>;
32
+ fetchSyncBlocksData(syncBlockNodes: SyncBlockNode[]): Promise<void>;
32
33
  private updateCacheWithSourceInfo;
33
34
  private updateCache;
34
35
  private updateSourceTitleSubscriptions;
35
- private getFromCache;
36
+ getFromCache(resourceId: ResourceId): SyncBlockInstance | undefined;
36
37
  private deleteFromCache;
37
38
  subscribeToSyncBlock(resourceId: string, localId: string, callback: SubscriptionCallback): () => void;
38
39
  subscribeToSourceTitle(node: PMNode, callback: TitleSubscriptionCallback): () => void;
@@ -10,3 +10,4 @@ export declare class SyncBlockStoreManager {
10
10
  get sourceManager(): SourceSyncBlockStoreManager;
11
11
  destroy(): void;
12
12
  }
13
+ export declare const useMemoizedSyncBlockStoreManager: (dataProvider?: SyncBlockDataProvider, fireAnalyticsEvent?: (payload: SyncBlockEventPayload) => void) => SyncBlockStoreManager;
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  "uuid": "^3.1.0"
34
34
  },
35
35
  "peerDependencies": {
36
- "@atlaskit/editor-common": "^110.42.0",
36
+ "@atlaskit/editor-common": "^110.48.0",
37
37
  "react": "^18.2.0"
38
38
  },
39
39
  "devDependencies": {
@@ -76,7 +76,7 @@
76
76
  }
77
77
  },
78
78
  "name": "@atlaskit/editor-synced-block-provider",
79
- "version": "2.15.4",
79
+ "version": "2.15.6",
80
80
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
81
81
  "author": "Atlassian Pty Ltd",
82
82
  "license": "Apache-2.0",