@atlaskit/editor-synced-block-provider 4.1.0 → 4.1.1

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.
@@ -12,7 +12,6 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
12
12
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
13
  var _isEqual = _interopRequireDefault(require("lodash/isEqual"));
14
14
  var _monitoring = require("@atlaskit/editor-common/monitoring");
15
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
16
15
  var _types = require("../common/types");
17
16
  var _errorHandling = require("../utils/errorHandling");
18
17
  var _experienceTracking = require("../utils/experienceTracking");
@@ -72,11 +71,9 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
72
71
  throw new Error('Local ID or resource ID is not set');
73
72
  }
74
73
  var syncBlockData = (0, _utils.convertSyncBlockPMNodeToSyncBlockData)(syncBlockNode);
75
- if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_5')) {
76
- var cachedBlock = this.syncBlockCache.get(resourceId);
77
- if (cachedBlock && !(0, _isEqual.default)(syncBlockData.content, cachedBlock.content)) {
78
- this.hasReceivedContentChange = true;
79
- }
74
+ var cachedBlock = this.syncBlockCache.get(resourceId);
75
+ if (cachedBlock && !(0, _isEqual.default)(syncBlockData.content, cachedBlock.content)) {
76
+ this.hasReceivedContentChange = true;
80
77
  }
81
78
  this.syncBlockCache.set(resourceId, _objectSpread(_objectSpread({}, syncBlockData), {}, {
82
79
  isDirty: true
@@ -9,26 +9,45 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
9
9
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _monitoring = require("@atlaskit/editor-common/monitoring");
12
- var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
12
  var _errorHandling = require("../utils/errorHandling");
14
13
  var _resolveSyncBlockInstance = require("../utils/resolveSyncBlockInstance");
15
14
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
16
15
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
17
16
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
17
+ 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; }
18
+ 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; }
18
19
  /**
19
20
  * Manages the lifecycle of GraphQL WebSocket subscriptions for sync block
20
- * real-time updates, and provides a listener API so React components can
21
- * react when the set of subscribed resource IDs changes.
21
+ * real-time updates, owns the subscriptions and titleSubscriptions maps,
22
+ * and provides a listener API so React components can react when the set
23
+ * of subscribed resource IDs changes.
22
24
  */
23
25
  var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__PURE__*/function () {
24
26
  function SyncBlockSubscriptionManager(deps) {
25
27
  (0, _classCallCheck2.default)(this, SyncBlockSubscriptionManager);
28
+ (0, _defineProperty2.default)(this, "subscriptions", new Map());
29
+ (0, _defineProperty2.default)(this, "titleSubscriptions", new Map());
26
30
  (0, _defineProperty2.default)(this, "graphqlSubscriptions", new Map());
27
31
  (0, _defineProperty2.default)(this, "subscriptionChangeListeners", new Set());
28
32
  (0, _defineProperty2.default)(this, "useRealTimeSubscriptions", false);
33
+ // Track pending cache deletions to handle block moves (unmount/remount)
34
+ // When a block is moved, the old component unmounts before the new one mounts,
35
+ // causing the cache to be deleted prematurely. We delay deletion to allow
36
+ // the new component to subscribe and cancel the pending deletion.
37
+ (0, _defineProperty2.default)(this, "pendingCacheDeletions", new Map());
29
38
  this.deps = deps;
30
39
  }
40
+
41
+ /**
42
+ * Returns the subscriptions map. Used by external consumers (e.g. batch fetcher, flush)
43
+ * that need to read the current subscription state.
44
+ */
31
45
  return (0, _createClass2.default)(SyncBlockSubscriptionManager, [{
46
+ key: "getSubscriptions",
47
+ value: function getSubscriptions() {
48
+ return this.subscriptions;
49
+ }
50
+ }, {
32
51
  key: "setRealTimeSubscriptionsEnabled",
33
52
  value: function setRealTimeSubscriptionsEnabled(enabled) {
34
53
  if (this.useRealTimeSubscriptions === enabled) {
@@ -49,7 +68,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
49
68
  }, {
50
69
  key: "getSubscribedResourceIds",
51
70
  value: function getSubscribedResourceIds() {
52
- return Array.from(this.deps.getSubscriptions().keys());
71
+ return Array.from(this.subscriptions.keys());
53
72
  }
54
73
  }, {
55
74
  key: "onSubscriptionsChanged",
@@ -89,10 +108,140 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
89
108
  this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
90
109
  }
91
110
  }
111
+ }, {
112
+ key: "subscribeToSyncBlock",
113
+ value: function subscribeToSyncBlock(resourceId, localId, callback) {
114
+ var _this3 = this;
115
+ // Cancel any pending cache deletion for this resourceId.
116
+ // This handles the case where a block is moved - the old component unmounts
117
+ // (scheduling deletion) but the new component mounts and subscribes before
118
+ // the deletion timeout fires.
119
+ var pendingDeletion = this.pendingCacheDeletions.get(resourceId);
120
+ if (pendingDeletion) {
121
+ clearTimeout(pendingDeletion);
122
+ this.pendingCacheDeletions.delete(resourceId);
123
+ }
124
+
125
+ // add to subscriptions map
126
+ var resourceSubscriptions = this.subscriptions.get(resourceId) || {};
127
+ var isNewResourceSubscription = Object.keys(resourceSubscriptions).length === 0;
128
+ this.subscriptions.set(resourceId, _objectSpread(_objectSpread({}, resourceSubscriptions), {}, (0, _defineProperty2.default)({}, localId, callback)));
129
+
130
+ // New subscription means new reference synced block is added to the document
131
+ this.deps.markCacheDirty();
132
+
133
+ // Notify listeners if this is a new resource subscription
134
+ if (isNewResourceSubscription) {
135
+ this.notifySubscriptionChangeListeners();
136
+ }
137
+ var cachedData = this.deps.getFromCache(resourceId);
138
+ if (cachedData) {
139
+ callback(cachedData);
140
+ } else {
141
+ this.deps.debouncedBatchedFetchSyncBlocks(resourceId);
142
+ }
143
+
144
+ // Set up GraphQL subscription if real-time subscriptions are enabled
145
+ if (this.shouldUseRealTime()) {
146
+ this.setupSubscription(resourceId);
147
+ }
148
+ return function () {
149
+ var resourceSubscriptions = _this3.subscriptions.get(resourceId);
150
+ if (resourceSubscriptions) {
151
+ // Unsubscription means a reference synced block is removed from the document
152
+ _this3.deps.markCacheDirty();
153
+ delete resourceSubscriptions[localId];
154
+ if (Object.keys(resourceSubscriptions).length === 0) {
155
+ _this3.subscriptions.delete(resourceId);
156
+
157
+ // Clean up GraphQL subscription when no more local subscribers
158
+ _this3.cleanupSubscription(resourceId);
159
+
160
+ // Notify listeners that subscription was removed
161
+ _this3.notifySubscriptionChangeListeners();
162
+
163
+ // Delay cache deletion to handle block moves (unmount/remount).
164
+ // When a block is moved, the old component unmounts before the new one mounts.
165
+ // By delaying deletion, we give the new component time to subscribe and
166
+ // cancel this pending deletion, preserving the cached data.
167
+ // TODO: EDITOR-4152 - Rework this logic
168
+ var deletionTimeout = setTimeout(function () {
169
+ // Only delete if still no subscribers (wasn't re-subscribed)
170
+ if (!_this3.subscriptions.has(resourceId)) {
171
+ _this3.deps.deleteFromCache(resourceId);
172
+ }
173
+ _this3.pendingCacheDeletions.delete(resourceId);
174
+ }, 1000);
175
+ _this3.pendingCacheDeletions.set(resourceId, deletionTimeout);
176
+ } else {
177
+ _this3.subscriptions.set(resourceId, resourceSubscriptions);
178
+ }
179
+ }
180
+ };
181
+ }
182
+ }, {
183
+ key: "subscribeToSourceTitle",
184
+ value: function subscribeToSourceTitle(node, callback) {
185
+ var _cachedData$data,
186
+ _this4 = this;
187
+ // check node is a sync block, as we only support sync block subscriptions
188
+ if (node.type.name !== 'syncBlock') {
189
+ return function () {};
190
+ }
191
+ var _node$attrs = node.attrs,
192
+ resourceId = _node$attrs.resourceId,
193
+ localId = _node$attrs.localId;
194
+ if (!localId || !resourceId) {
195
+ return function () {};
196
+ }
197
+ var cachedData = this.deps.getFromCache(resourceId);
198
+ if (cachedData !== null && cachedData !== void 0 && (_cachedData$data = cachedData.data) !== null && _cachedData$data !== void 0 && _cachedData$data.sourceTitle) {
199
+ callback(cachedData.data.sourceTitle);
200
+ }
201
+
202
+ // add to subscriptions map
203
+ var resourceSubscriptions = this.titleSubscriptions.get(resourceId) || {};
204
+ this.titleSubscriptions.set(resourceId, _objectSpread(_objectSpread({}, resourceSubscriptions), {}, (0, _defineProperty2.default)({}, localId, callback)));
205
+ return function () {
206
+ var resourceSubscriptions = _this4.titleSubscriptions.get(resourceId);
207
+ if (resourceSubscriptions) {
208
+ delete resourceSubscriptions[localId];
209
+ if (Object.keys(resourceSubscriptions).length === 0) {
210
+ _this4.titleSubscriptions.delete(resourceId);
211
+ } else {
212
+ _this4.titleSubscriptions.set(resourceId, resourceSubscriptions);
213
+ }
214
+ }
215
+ };
216
+ }
217
+ }, {
218
+ key: "updateSourceTitleSubscriptions",
219
+ value: function updateSourceTitleSubscriptions(resourceId, title) {
220
+ var callbacks = this.titleSubscriptions.get(resourceId);
221
+ if (callbacks) {
222
+ Object.values(callbacks).forEach(function (callback) {
223
+ callback(title);
224
+ });
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Notifies all subscription callbacks for a given resource ID with the provided sync block instance.
230
+ */
231
+ }, {
232
+ key: "notifySubscriptionCallbacks",
233
+ value: function notifySubscriptionCallbacks(resourceId, syncBlock) {
234
+ var callbacks = this.subscriptions.get(resourceId);
235
+ if (callbacks) {
236
+ Object.values(callbacks).forEach(function (callback) {
237
+ callback(syncBlock);
238
+ });
239
+ }
240
+ }
92
241
  }, {
93
242
  key: "setupSubscription",
94
243
  value: function setupSubscription(resourceId) {
95
- var _this3 = this;
244
+ var _this5 = this;
96
245
  if (this.graphqlSubscriptions.has(resourceId)) {
97
246
  return;
98
247
  }
@@ -101,13 +250,13 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
101
250
  return;
102
251
  }
103
252
  var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
104
- _this3.handleGraphQLUpdate(syncBlockInstance);
253
+ _this5.handleGraphQLUpdate(syncBlockInstance);
105
254
  }, function (error) {
106
- var _this3$deps$getFireAn;
255
+ var _this5$deps$getFireAn;
107
256
  (0, _monitoring.logException)(error, {
108
257
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
109
258
  });
110
- (_this3$deps$getFireAn = _this3.deps.getFireAnalyticsEvent()) === null || _this3$deps$getFireAn === void 0 || _this3$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message));
259
+ (_this5$deps$getFireAn = _this5.deps.getFireAnalyticsEvent()) === null || _this5$deps$getFireAn === void 0 || _this5$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message));
111
260
  });
112
261
  if (unsubscribe) {
113
262
  this.graphqlSubscriptions.set(resourceId, unsubscribe);
@@ -125,7 +274,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
125
274
  }, {
126
275
  key: "setupSubscriptionsForAllBlocks",
127
276
  value: function setupSubscriptionsForAllBlocks() {
128
- var _iterator = _createForOfIteratorHelper(this.deps.getSubscriptions().keys()),
277
+ var _iterator = _createForOfIteratorHelper(this.subscriptions.keys()),
129
278
  _step;
130
279
  try {
131
280
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -159,8 +308,25 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
159
308
  key: "destroy",
160
309
  value: function destroy() {
161
310
  this.cleanupAll();
311
+ this.subscriptions.clear();
312
+ this.titleSubscriptions.clear();
162
313
  this.subscriptionChangeListeners.clear();
163
314
  this.useRealTimeSubscriptions = false;
315
+
316
+ // Clear any pending cache deletions
317
+ var _iterator3 = _createForOfIteratorHelper(this.pendingCacheDeletions.values()),
318
+ _step3;
319
+ try {
320
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
321
+ var timeout = _step3.value;
322
+ clearTimeout(timeout);
323
+ }
324
+ } catch (err) {
325
+ _iterator3.e(err);
326
+ } finally {
327
+ _iterator3.f();
328
+ }
329
+ this.pendingCacheDeletions.clear();
164
330
  }
165
331
  }, {
166
332
  key: "shouldUseRealTime",
@@ -170,22 +336,19 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
170
336
  }, {
171
337
  key: "handleGraphQLUpdate",
172
338
  value: function handleGraphQLUpdate(syncBlockInstance) {
173
- var _this4 = this;
339
+ var _this6 = this;
174
340
  if (!syncBlockInstance.resourceId) {
175
- if ((0, _platformFeatureFlags.fg)('platform_synced_block_patch_5')) {
176
- return;
177
- }
178
- throw new Error('Sync block instance provided to graphql subscription update missing resource id');
341
+ return;
179
342
  }
180
343
  var existing = this.deps.getFromCache(syncBlockInstance.resourceId);
181
344
  var resolved = existing ? (0, _resolveSyncBlockInstance.resolveSyncBlockInstance)(existing, syncBlockInstance) : syncBlockInstance;
182
345
  this.deps.updateCache(resolved);
183
346
  if (!syncBlockInstance.error) {
184
- var callbacks = this.deps.getSubscriptions().get(syncBlockInstance.resourceId);
347
+ var callbacks = this.subscriptions.get(syncBlockInstance.resourceId);
185
348
  var localIds = callbacks ? Object.keys(callbacks) : [];
186
349
  localIds.forEach(function (localId) {
187
- var _this4$deps$getFireAn, _syncBlockInstance$da;
188
- (_this4$deps$getFireAn = _this4.deps.getFireAnalyticsEvent()) === null || _this4$deps$getFireAn === void 0 || _this4$deps$getFireAn((0, _errorHandling.fetchSuccessPayload)(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = syncBlockInstance.data) === null || _syncBlockInstance$da === void 0 ? void 0 : _syncBlockInstance$da.product));
350
+ var _this6$deps$getFireAn, _syncBlockInstance$da;
351
+ (_this6$deps$getFireAn = _this6.deps.getFireAnalyticsEvent()) === null || _this6$deps$getFireAn === void 0 || _this6$deps$getFireAn((0, _errorHandling.fetchSuccessPayload)(syncBlockInstance.resourceId, localId, (_syncBlockInstance$da = syncBlockInstance.data) === null || _syncBlockInstance$da === void 0 ? void 0 : _syncBlockInstance$da.product));
189
352
  });
190
353
  this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
191
354
  } else {