@atlaskit/editor-synced-block-provider 4.1.0 → 4.1.2
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 +14 -0
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +209 -811
- package/dist/cjs/store-manager/sourceSyncBlockStoreManager.js +3 -6
- package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +180 -17
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +78 -595
- package/dist/es2019/store-manager/sourceSyncBlockStoreManager.js +3 -6
- package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +157 -10
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +210 -812
- package/dist/esm/store-manager/sourceSyncBlockStoreManager.js +3 -6
- package/dist/esm/store-manager/syncBlockSubscriptionManager.js +180 -17
- package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +4 -49
- package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +25 -6
- package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +4 -49
- package/dist/types-ts4.5/store-manager/syncBlockSubscriptionManager.d.ts +25 -6
- package/package.json +4 -8
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import isEqual from 'lodash/isEqual';
|
|
3
3
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
4
|
-
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
4
|
import { SyncBlockError } from '../common/types';
|
|
6
5
|
import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload } from '../utils/errorHandling';
|
|
7
6
|
import { getCreateSourceExperience, getDeleteSourceExperience, getSaveSourceExperience, getFetchSourceInfoExperience } from '../utils/experienceTracking';
|
|
@@ -52,11 +51,9 @@ export class SourceSyncBlockStoreManager {
|
|
|
52
51
|
throw new Error('Local ID or resource ID is not set');
|
|
53
52
|
}
|
|
54
53
|
const syncBlockData = convertSyncBlockPMNodeToSyncBlockData(syncBlockNode);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.hasReceivedContentChange = true;
|
|
59
|
-
}
|
|
54
|
+
const cachedBlock = this.syncBlockCache.get(resourceId);
|
|
55
|
+
if (cachedBlock && !isEqual(syncBlockData.content, cachedBlock.content)) {
|
|
56
|
+
this.hasReceivedContentChange = true;
|
|
60
57
|
}
|
|
61
58
|
this.syncBlockCache.set(resourceId, {
|
|
62
59
|
...syncBlockData,
|
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
3
|
-
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
3
|
import { fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
|
|
5
4
|
import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
|
|
6
5
|
/**
|
|
7
6
|
* Manages the lifecycle of GraphQL WebSocket subscriptions for sync block
|
|
8
|
-
* real-time updates,
|
|
9
|
-
*
|
|
7
|
+
* real-time updates, owns the subscriptions and titleSubscriptions maps,
|
|
8
|
+
* and provides a listener API so React components can react when the set
|
|
9
|
+
* of subscribed resource IDs changes.
|
|
10
10
|
*/
|
|
11
11
|
export class SyncBlockSubscriptionManager {
|
|
12
12
|
constructor(deps) {
|
|
13
|
+
_defineProperty(this, "subscriptions", new Map());
|
|
14
|
+
_defineProperty(this, "titleSubscriptions", new Map());
|
|
13
15
|
_defineProperty(this, "graphqlSubscriptions", new Map());
|
|
14
16
|
_defineProperty(this, "subscriptionChangeListeners", new Set());
|
|
15
17
|
_defineProperty(this, "useRealTimeSubscriptions", false);
|
|
18
|
+
// Track pending cache deletions to handle block moves (unmount/remount)
|
|
19
|
+
// When a block is moved, the old component unmounts before the new one mounts,
|
|
20
|
+
// causing the cache to be deleted prematurely. We delay deletion to allow
|
|
21
|
+
// the new component to subscribe and cancel the pending deletion.
|
|
22
|
+
_defineProperty(this, "pendingCacheDeletions", new Map());
|
|
16
23
|
this.deps = deps;
|
|
17
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns the subscriptions map. Used by external consumers (e.g. batch fetcher, flush)
|
|
28
|
+
* that need to read the current subscription state.
|
|
29
|
+
*/
|
|
30
|
+
getSubscriptions() {
|
|
31
|
+
return this.subscriptions;
|
|
32
|
+
}
|
|
18
33
|
setRealTimeSubscriptionsEnabled(enabled) {
|
|
19
34
|
if (this.useRealTimeSubscriptions === enabled) {
|
|
20
35
|
return;
|
|
@@ -30,7 +45,7 @@ export class SyncBlockSubscriptionManager {
|
|
|
30
45
|
return this.useRealTimeSubscriptions;
|
|
31
46
|
}
|
|
32
47
|
getSubscribedResourceIds() {
|
|
33
|
-
return Array.from(this.
|
|
48
|
+
return Array.from(this.subscriptions.keys());
|
|
34
49
|
}
|
|
35
50
|
onSubscriptionsChanged(listener) {
|
|
36
51
|
this.subscriptionChangeListeners.add(listener);
|
|
@@ -62,6 +77,133 @@ export class SyncBlockSubscriptionManager {
|
|
|
62
77
|
this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
|
|
63
78
|
}
|
|
64
79
|
}
|
|
80
|
+
subscribeToSyncBlock(resourceId, localId, callback) {
|
|
81
|
+
// Cancel any pending cache deletion for this resourceId.
|
|
82
|
+
// This handles the case where a block is moved - the old component unmounts
|
|
83
|
+
// (scheduling deletion) but the new component mounts and subscribes before
|
|
84
|
+
// the deletion timeout fires.
|
|
85
|
+
const pendingDeletion = this.pendingCacheDeletions.get(resourceId);
|
|
86
|
+
if (pendingDeletion) {
|
|
87
|
+
clearTimeout(pendingDeletion);
|
|
88
|
+
this.pendingCacheDeletions.delete(resourceId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// add to subscriptions map
|
|
92
|
+
const resourceSubscriptions = this.subscriptions.get(resourceId) || {};
|
|
93
|
+
const isNewResourceSubscription = Object.keys(resourceSubscriptions).length === 0;
|
|
94
|
+
this.subscriptions.set(resourceId, {
|
|
95
|
+
...resourceSubscriptions,
|
|
96
|
+
[localId]: callback
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// New subscription means new reference synced block is added to the document
|
|
100
|
+
this.deps.markCacheDirty();
|
|
101
|
+
|
|
102
|
+
// Notify listeners if this is a new resource subscription
|
|
103
|
+
if (isNewResourceSubscription) {
|
|
104
|
+
this.notifySubscriptionChangeListeners();
|
|
105
|
+
}
|
|
106
|
+
const cachedData = this.deps.getFromCache(resourceId);
|
|
107
|
+
if (cachedData) {
|
|
108
|
+
callback(cachedData);
|
|
109
|
+
} else {
|
|
110
|
+
this.deps.debouncedBatchedFetchSyncBlocks(resourceId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Set up GraphQL subscription if real-time subscriptions are enabled
|
|
114
|
+
if (this.shouldUseRealTime()) {
|
|
115
|
+
this.setupSubscription(resourceId);
|
|
116
|
+
}
|
|
117
|
+
return () => {
|
|
118
|
+
const resourceSubscriptions = this.subscriptions.get(resourceId);
|
|
119
|
+
if (resourceSubscriptions) {
|
|
120
|
+
// Unsubscription means a reference synced block is removed from the document
|
|
121
|
+
this.deps.markCacheDirty();
|
|
122
|
+
delete resourceSubscriptions[localId];
|
|
123
|
+
if (Object.keys(resourceSubscriptions).length === 0) {
|
|
124
|
+
this.subscriptions.delete(resourceId);
|
|
125
|
+
|
|
126
|
+
// Clean up GraphQL subscription when no more local subscribers
|
|
127
|
+
this.cleanupSubscription(resourceId);
|
|
128
|
+
|
|
129
|
+
// Notify listeners that subscription was removed
|
|
130
|
+
this.notifySubscriptionChangeListeners();
|
|
131
|
+
|
|
132
|
+
// Delay cache deletion to handle block moves (unmount/remount).
|
|
133
|
+
// When a block is moved, the old component unmounts before the new one mounts.
|
|
134
|
+
// By delaying deletion, we give the new component time to subscribe and
|
|
135
|
+
// cancel this pending deletion, preserving the cached data.
|
|
136
|
+
// TODO: EDITOR-4152 - Rework this logic
|
|
137
|
+
const deletionTimeout = setTimeout(() => {
|
|
138
|
+
// Only delete if still no subscribers (wasn't re-subscribed)
|
|
139
|
+
if (!this.subscriptions.has(resourceId)) {
|
|
140
|
+
this.deps.deleteFromCache(resourceId);
|
|
141
|
+
}
|
|
142
|
+
this.pendingCacheDeletions.delete(resourceId);
|
|
143
|
+
}, 1000);
|
|
144
|
+
this.pendingCacheDeletions.set(resourceId, deletionTimeout);
|
|
145
|
+
} else {
|
|
146
|
+
this.subscriptions.set(resourceId, resourceSubscriptions);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
subscribeToSourceTitle(node, callback) {
|
|
152
|
+
var _cachedData$data;
|
|
153
|
+
// check node is a sync block, as we only support sync block subscriptions
|
|
154
|
+
if (node.type.name !== 'syncBlock') {
|
|
155
|
+
return () => {};
|
|
156
|
+
}
|
|
157
|
+
const {
|
|
158
|
+
resourceId,
|
|
159
|
+
localId
|
|
160
|
+
} = node.attrs;
|
|
161
|
+
if (!localId || !resourceId) {
|
|
162
|
+
return () => {};
|
|
163
|
+
}
|
|
164
|
+
const cachedData = this.deps.getFromCache(resourceId);
|
|
165
|
+
if (cachedData !== null && cachedData !== void 0 && (_cachedData$data = cachedData.data) !== null && _cachedData$data !== void 0 && _cachedData$data.sourceTitle) {
|
|
166
|
+
callback(cachedData.data.sourceTitle);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// add to subscriptions map
|
|
170
|
+
const resourceSubscriptions = this.titleSubscriptions.get(resourceId) || {};
|
|
171
|
+
this.titleSubscriptions.set(resourceId, {
|
|
172
|
+
...resourceSubscriptions,
|
|
173
|
+
[localId]: callback
|
|
174
|
+
});
|
|
175
|
+
return () => {
|
|
176
|
+
const resourceSubscriptions = this.titleSubscriptions.get(resourceId);
|
|
177
|
+
if (resourceSubscriptions) {
|
|
178
|
+
delete resourceSubscriptions[localId];
|
|
179
|
+
if (Object.keys(resourceSubscriptions).length === 0) {
|
|
180
|
+
this.titleSubscriptions.delete(resourceId);
|
|
181
|
+
} else {
|
|
182
|
+
this.titleSubscriptions.set(resourceId, resourceSubscriptions);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
updateSourceTitleSubscriptions(resourceId, title) {
|
|
188
|
+
const callbacks = this.titleSubscriptions.get(resourceId);
|
|
189
|
+
if (callbacks) {
|
|
190
|
+
Object.values(callbacks).forEach(callback => {
|
|
191
|
+
callback(title);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Notifies all subscription callbacks for a given resource ID with the provided sync block instance.
|
|
198
|
+
*/
|
|
199
|
+
notifySubscriptionCallbacks(resourceId, syncBlock) {
|
|
200
|
+
const callbacks = this.subscriptions.get(resourceId);
|
|
201
|
+
if (callbacks) {
|
|
202
|
+
Object.values(callbacks).forEach(callback => {
|
|
203
|
+
callback(syncBlock);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
65
207
|
setupSubscription(resourceId) {
|
|
66
208
|
if (this.graphqlSubscriptions.has(resourceId)) {
|
|
67
209
|
return;
|
|
@@ -91,7 +233,7 @@ export class SyncBlockSubscriptionManager {
|
|
|
91
233
|
}
|
|
92
234
|
}
|
|
93
235
|
setupSubscriptionsForAllBlocks() {
|
|
94
|
-
for (const resourceId of this.
|
|
236
|
+
for (const resourceId of this.subscriptions.keys()) {
|
|
95
237
|
this.setupSubscription(resourceId);
|
|
96
238
|
}
|
|
97
239
|
}
|
|
@@ -103,24 +245,29 @@ export class SyncBlockSubscriptionManager {
|
|
|
103
245
|
}
|
|
104
246
|
destroy() {
|
|
105
247
|
this.cleanupAll();
|
|
248
|
+
this.subscriptions.clear();
|
|
249
|
+
this.titleSubscriptions.clear();
|
|
106
250
|
this.subscriptionChangeListeners.clear();
|
|
107
251
|
this.useRealTimeSubscriptions = false;
|
|
252
|
+
|
|
253
|
+
// Clear any pending cache deletions
|
|
254
|
+
for (const timeout of this.pendingCacheDeletions.values()) {
|
|
255
|
+
clearTimeout(timeout);
|
|
256
|
+
}
|
|
257
|
+
this.pendingCacheDeletions.clear();
|
|
108
258
|
}
|
|
109
259
|
shouldUseRealTime() {
|
|
110
260
|
return this.useRealTimeSubscriptions;
|
|
111
261
|
}
|
|
112
262
|
handleGraphQLUpdate(syncBlockInstance) {
|
|
113
263
|
if (!syncBlockInstance.resourceId) {
|
|
114
|
-
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
throw new Error('Sync block instance provided to graphql subscription update missing resource id');
|
|
264
|
+
return;
|
|
118
265
|
}
|
|
119
266
|
const existing = this.deps.getFromCache(syncBlockInstance.resourceId);
|
|
120
267
|
const resolved = existing ? resolveSyncBlockInstance(existing, syncBlockInstance) : syncBlockInstance;
|
|
121
268
|
this.deps.updateCache(resolved);
|
|
122
269
|
if (!syncBlockInstance.error) {
|
|
123
|
-
const callbacks = this.
|
|
270
|
+
const callbacks = this.subscriptions.get(syncBlockInstance.resourceId);
|
|
124
271
|
const localIds = callbacks ? Object.keys(callbacks) : [];
|
|
125
272
|
localIds.forEach(localId => {
|
|
126
273
|
var _this$deps$getFireAna3, _syncBlockInstance$da;
|