@atlaskit/editor-synced-block-provider 2.2.3 → 2.3.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/hooks/useFetchSyncBlockData.js +5 -30
  3. package/dist/cjs/providers/confluence/confluenceContentAPI.js +50 -0
  4. package/dist/cjs/providers/in-memory/inMemory.js +7 -0
  5. package/dist/cjs/providers/syncBlockProvider.js +75 -19
  6. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +319 -63
  7. package/dist/cjs/store-manager/sourceSyncBlockStoreManager.js +35 -11
  8. package/dist/cjs/store-manager/syncBlockStoreManager.js +27 -4
  9. package/dist/cjs/utils/contentProperty.js +54 -1
  10. package/dist/cjs/utils/mergeFetchSyncBlockDataResult.js +38 -0
  11. package/dist/es2019/hooks/useFetchSyncBlockData.js +6 -31
  12. package/dist/es2019/providers/confluence/confluenceContentAPI.js +31 -1
  13. package/dist/es2019/providers/in-memory/inMemory.js +7 -0
  14. package/dist/es2019/providers/syncBlockProvider.js +31 -3
  15. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +172 -44
  16. package/dist/es2019/store-manager/sourceSyncBlockStoreManager.js +16 -4
  17. package/dist/es2019/store-manager/syncBlockStoreManager.js +21 -4
  18. package/dist/es2019/utils/contentProperty.js +54 -0
  19. package/dist/es2019/utils/mergeFetchSyncBlockDataResult.js +30 -0
  20. package/dist/esm/hooks/useFetchSyncBlockData.js +6 -31
  21. package/dist/esm/providers/confluence/confluenceContentAPI.js +51 -1
  22. package/dist/esm/providers/in-memory/inMemory.js +7 -0
  23. package/dist/esm/providers/syncBlockProvider.js +73 -17
  24. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +320 -64
  25. package/dist/esm/store-manager/sourceSyncBlockStoreManager.js +35 -11
  26. package/dist/esm/store-manager/syncBlockStoreManager.js +27 -4
  27. package/dist/esm/utils/contentProperty.js +53 -0
  28. package/dist/esm/utils/mergeFetchSyncBlockDataResult.js +31 -0
  29. package/dist/types/common/schema.d.ts +1 -1
  30. package/dist/types/providers/confluence/confluenceContentAPI.d.ts +2 -1
  31. package/dist/types/providers/syncBlockProvider.d.ts +3 -2
  32. package/dist/types/providers/types.d.ts +8 -0
  33. package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +24 -8
  34. package/dist/types/store-manager/sourceSyncBlockStoreManager.d.ts +1 -1
  35. package/dist/types/store-manager/syncBlockStoreManager.d.ts +12 -4
  36. package/dist/types/utils/contentProperty.d.ts +35 -0
  37. package/dist/types/utils/mergeFetchSyncBlockDataResult.d.ts +12 -0
  38. package/dist/types-ts4.5/common/schema.d.ts +1 -1
  39. package/dist/types-ts4.5/providers/confluence/confluenceContentAPI.d.ts +2 -1
  40. package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +3 -2
  41. package/dist/types-ts4.5/providers/types.d.ts +8 -0
  42. package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +24 -8
  43. package/dist/types-ts4.5/store-manager/sourceSyncBlockStoreManager.d.ts +1 -1
  44. package/dist/types-ts4.5/store-manager/syncBlockStoreManager.d.ts +12 -4
  45. package/dist/types-ts4.5/utils/contentProperty.d.ts +35 -0
  46. package/dist/types-ts4.5/utils/mergeFetchSyncBlockDataResult.d.ts +12 -0
  47. package/package.json +1 -1
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.resolveFetchSyncBlockDataResult = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ 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; }
10
+ 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; }
11
+ /**
12
+ * Merges two FetchSyncBlockDataResult objects,
13
+ * currently it only preserves the sourceURL from the old result,
14
+ * but this can be extended in the future to preserve other fields and resolve conflicts as needed.
15
+ * e.g. compare timestamps or version numbers to determine which data is more recent.
16
+ *
17
+ * @param oldResult - The existing FetchSyncBlockDataResult object.
18
+ * @param newResult - The new FetchSyncBlockDataResult object to merge.
19
+ * @returns A merged FetchSyncBlockDataResult object.
20
+ */
21
+ var resolveFetchSyncBlockDataResult = exports.resolveFetchSyncBlockDataResult = function resolveFetchSyncBlockDataResult(oldResult, newResult) {
22
+ var _newResult$data, _oldResult$data;
23
+ // if the old result has no data, we simple return the new result
24
+ if (!oldResult.data) {
25
+ return newResult;
26
+ } else if (!newResult.data) {
27
+ // if the new result has no data, we simply return the old result
28
+ // TODO: EDITOR-2533 - handle this case based on the error type and whether we should keep old data or not
29
+ return oldResult;
30
+ }
31
+
32
+ // otherwise, we merge the two results, preserving the sourceURL from the old result if it exists
33
+ return _objectSpread(_objectSpread({}, newResult), {}, {
34
+ data: _objectSpread(_objectSpread({}, newResult.data), {}, {
35
+ sourceURL: ((_newResult$data = newResult.data) === null || _newResult$data === void 0 ? void 0 : _newResult$data.sourceURL) || ((_oldResult$data = oldResult.data) === null || _oldResult$data === void 0 ? void 0 : _oldResult$data.sourceURL) || undefined
36
+ })
37
+ });
38
+ };
@@ -1,39 +1,14 @@
1
- import { useCallback, useEffect, useState } from 'react';
2
- import { SyncBlockError } from '../common/types';
1
+ import { useEffect, useState } from 'react';
3
2
  export const SYNC_BLOCK_FETCH_INTERVAL = 3000;
4
3
  export const useFetchSyncBlockData = (manager, syncBlockNode) => {
5
4
  const [fetchSyncBlockDataResult, setFetchSyncBlockDataResult] = useState(null);
6
- const fetchSyncBlockNode = useCallback(() => {
7
- manager.fetchSyncBlockData(syncBlockNode).then(data => {
8
- if (data !== null && data !== void 0 && data.error) {
9
- // if there is an error, we don't want to replace real existing data with the error data
10
- setFetchSyncBlockDataResult(prev => {
11
- if (!prev || prev.error) {
12
- return data;
13
- }
14
- return prev;
15
- });
16
- } else {
17
- setFetchSyncBlockDataResult(data !== null && data !== void 0 ? data : null);
18
- }
19
- }).catch(() => {
20
- //TODO: EDITOR-1921 - add error analytics
21
- setFetchSyncBlockDataResult(prev => {
22
- if (!prev || prev.error) {
23
- return {
24
- error: SyncBlockError.Errored
25
- };
26
- }
27
- return prev;
28
- });
29
- });
30
- }, [manager, syncBlockNode]);
31
5
  useEffect(() => {
32
- fetchSyncBlockNode();
33
- const interval = window.setInterval(fetchSyncBlockNode, SYNC_BLOCK_FETCH_INTERVAL);
6
+ const unsubscribe = manager.subscribeToSyncBlockData(syncBlockNode, data => {
7
+ setFetchSyncBlockDataResult(data);
8
+ });
34
9
  return () => {
35
- window.clearInterval(interval);
10
+ unsubscribe();
36
11
  };
37
- }, [fetchSyncBlockNode]);
12
+ }, [manager, setFetchSyncBlockDataResult, syncBlockNode]);
38
13
  return fetchSyncBlockDataResult;
39
14
  };
@@ -2,7 +2,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { useMemo } from 'react';
3
3
  import { SyncBlockError } from '../../common/types';
4
4
  import { getLocalIdFromAri, getPageIdAndTypeFromAri } from '../../utils/ari';
5
- import { getContentProperty, createContentProperty, updateContentProperty } from '../../utils/contentProperty';
5
+ import { getContentProperty, createContentProperty, updateContentProperty, deleteContentProperty } from '../../utils/contentProperty';
6
6
  import { isBlogPageType } from '../../utils/utils';
7
7
 
8
8
  /**
@@ -175,6 +175,36 @@ class ConfluenceADFWriteProvider {
175
175
  return this.createNewContentProperty(pageId, key, data, pageType);
176
176
  }
177
177
  }
178
+ async deleteData(resourceId) {
179
+ const {
180
+ id: pageId,
181
+ type: pageType
182
+ } = getPageIdAndTypeFromAri(resourceId);
183
+ const localId = getLocalIdFromAri(resourceId);
184
+ const key = getContentPropertyKey(this.config.contentPropertyKey, localId);
185
+ const options = {
186
+ pageId,
187
+ key,
188
+ cloudId: this.config.cloudId,
189
+ pageType
190
+ };
191
+ let deletePayload, deleteResult;
192
+ try {
193
+ deletePayload = await deleteContentProperty(options);
194
+ deleteResult = isBlogPageType(pageType) ? deletePayload.data.confluence.deleteBlogPostProperty : deletePayload.data.confluence.deletePageProperty;
195
+ } catch {
196
+ return {
197
+ resourceId,
198
+ success: false,
199
+ error: `Fail to delete ${pageType} content property`
200
+ };
201
+ }
202
+ return {
203
+ resourceId,
204
+ success: deleteResult.success,
205
+ error: deleteResult.errors.join()
206
+ };
207
+ }
178
208
  }
179
209
 
180
210
  /**
@@ -21,5 +21,12 @@ export const inMemoryWriteProvider = {
21
21
  inMemStore.set(uuid, data);
22
22
  return Promise.resolve(uuid);
23
23
  }
24
+ },
25
+ deleteData: resourceId => {
26
+ const success = inMemStore.delete(resourceId);
27
+ return Promise.resolve({
28
+ resourceId,
29
+ success
30
+ });
24
31
  }
25
32
  };
@@ -1,5 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { useMemo } from 'react';
3
+ import { SyncBlockError } from '../common/types';
3
4
  import { SyncBlockDataProvider } from '../providers/types';
4
5
  import { getLocalIdFromAri, getPageARIFromResourceId } from '../utils/ari';
5
6
  export class SyncBlockProvider extends SyncBlockDataProvider {
@@ -17,9 +18,22 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
17
18
  return node.attrs.localId;
18
19
  }
19
20
  fetchNodesData(nodes) {
20
- return Promise.all(nodes.map(node => {
21
- return this.fetchProvider.fetchData(node.attrs.resourceId);
22
- }));
21
+ const resourceIdSet = new Set(nodes.map(node => node.attrs.resourceId));
22
+ const resourceIds = [...resourceIdSet];
23
+ return Promise.allSettled(resourceIds.map(resourceId => {
24
+ return this.fetchProvider.fetchData(resourceId).then(data => {
25
+ return data;
26
+ }, () => {
27
+ return {
28
+ status: SyncBlockError.Errored,
29
+ resourceId
30
+ };
31
+ });
32
+ })).then(results => {
33
+ return results.filter(result => {
34
+ return result.status === 'fulfilled';
35
+ }).map(result => result.value);
36
+ });
23
37
  }
24
38
 
25
39
  /**
@@ -41,6 +55,20 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
41
55
  });
42
56
  return Promise.all(resourceIds);
43
57
  }
58
+ async deleteNodesData(resourceIds) {
59
+ const results = await Promise.allSettled(resourceIds.map(resourceId => this.writeProvider.deleteData(resourceId)));
60
+ return results.map((result, index) => {
61
+ if (result.status === 'fulfilled') {
62
+ return result.value;
63
+ } else {
64
+ return {
65
+ resourceId: resourceIds[index],
66
+ success: false,
67
+ error: result.reason
68
+ };
69
+ }
70
+ });
71
+ }
44
72
  getSourceId() {
45
73
  return this.sourceId;
46
74
  }
@@ -1,3 +1,5 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { resolveFetchSyncBlockDataResult } from '../utils/mergeFetchSyncBlockDataResult';
1
3
  const createSyncBlockNode = (localId, resourceId) => {
2
4
  return {
3
5
  type: 'syncBlock',
@@ -9,80 +11,206 @@ const createSyncBlockNode = (localId, resourceId) => {
9
11
  };
10
12
  export class ReferenceSyncBlockStoreManager {
11
13
  constructor(dataProvider) {
12
- this.dataProvider = dataProvider;
14
+ _defineProperty(this, "isInitialized", false);
15
+ _defineProperty(this, "isRefreshingSubscriptions", false);
13
16
  this.syncBlockCache = new Map();
17
+ this.subscriptions = new Map();
18
+ this.dataProvider = dataProvider;
14
19
  this.syncBlockURLRequests = new Map();
15
20
  }
21
+ async init(editorView) {
22
+ if (!this.editorView && !this.isInitialized) {
23
+ this.editorView = editorView;
24
+ const syncBlockNodes = editorView.state.doc.children.filter(node => node.type.name === 'syncBlock').map(node => {
25
+ return node.toJSON();
26
+ }) || [];
27
+ if (syncBlockNodes.length > 0) {
28
+ try {
29
+ const dataResults = await this.fetchSyncBlocksData(syncBlockNodes);
30
+ if (!dataResults) {
31
+ throw new Error('No data results returned when initializing sync block store manager');
32
+ }
33
+ dataResults.forEach(dataResult => {
34
+ this.updateCache(dataResult);
35
+ });
36
+ } catch (error) {
37
+ // TODO: EDITOR-1921 - add error analytics
38
+ }
39
+ }
40
+ }
41
+ this.isInitialized = true;
42
+ }
16
43
 
17
44
  /**
18
- *
19
- * @param localId - The local ID of the sync block to get the source URL for
20
- * @param resourceId - The resource ID of the sync block to get the source URL for
21
- * Fetches source URl for a sync block and updates sync block data with the source URL asynchronously.
45
+ * Refreshes the subscriptions for all sync blocks.
46
+ * @returns {Promise<void>}
22
47
  */
23
- fetchSyncBlockSourceURL({
24
- localId,
25
- resourceId
26
- }) {
27
- if (!localId || !resourceId || !this.dataProvider) {
48
+ async refreshSubscriptions() {
49
+ if (this.isRefreshingSubscriptions || !this.isInitialized) {
50
+ return;
51
+ }
52
+ this.isRefreshingSubscriptions = true;
53
+ const syncBlocks = [];
54
+ for (const [resourceId, callbacks] of this.subscriptions.entries()) {
55
+ Object.keys(callbacks).forEach(localId => {
56
+ syncBlocks.push(createSyncBlockNode(localId, resourceId));
57
+ });
58
+ }
59
+ try {
60
+ // fetch latest data for all subscribed sync blocks
61
+ // this function will update the cache and call the subscriptions
62
+ await this.fetchSyncBlocksData(syncBlocks);
63
+ } catch (error) {
64
+ // TODO: EDITOR-1921 - add error analytics
65
+ } finally {
66
+ this.isRefreshingSubscriptions = false;
67
+ }
68
+ }
69
+ fetchSyncBlockSourceURL(resourceId) {
70
+ if (!resourceId || !this.dataProvider) {
28
71
  return;
29
72
  }
30
73
 
31
74
  // if the sync block is a reference block, we need to fetch the URL to the source
32
75
  // we could optimise this further by checking if the sync block is on the same page as the source
33
- if (!this.syncBlockURLRequests.get(localId)) {
34
- this.syncBlockURLRequests.set(localId, true);
35
- this.dataProvider.retrieveSyncBlockSourceUrl(createSyncBlockNode(localId, resourceId)).then(sourceURL => {
36
- const existingSyncBlock = this.syncBlockCache.get(localId);
37
- if (existingSyncBlock) {
38
- existingSyncBlock.sourceURL = sourceURL;
76
+ if (!this.syncBlockURLRequests.get(resourceId)) {
77
+ this.syncBlockURLRequests.set(resourceId, true);
78
+ this.dataProvider.retrieveSyncBlockSourceUrl(createSyncBlockNode('', resourceId)).then(sourceURL => {
79
+ const existingSyncBlock = this.getFromCache(resourceId);
80
+ if (existingSyncBlock && existingSyncBlock.data) {
81
+ existingSyncBlock.data = {
82
+ ...existingSyncBlock.data,
83
+ sourceURL
84
+ };
85
+ this.updateCache(existingSyncBlock);
39
86
  }
40
87
  }).finally(() => {
41
- this.syncBlockURLRequests.set(localId, false);
88
+ this.syncBlockURLRequests.set(resourceId, false);
42
89
  });
43
90
  }
44
91
  }
92
+
93
+ /**
94
+ * Fetch sync block data for a given sync block node.
95
+ * @param syncBlockNode - The sync block node to fetch data for
96
+ * @returns The fetched sync block data result
97
+ */
45
98
  async fetchSyncBlockData(syncBlockNode) {
46
99
  if (!this.dataProvider) {
47
100
  throw new Error('Data provider not set');
48
101
  }
49
102
  const syncNode = createSyncBlockNode(syncBlockNode.attrs.localId, syncBlockNode.attrs.resourceId);
50
-
51
- // async fetch source URL if it is not already fetched
52
- const existingSyncBlock = this.syncBlockCache.get(syncBlockNode.attrs.localId);
53
- if (!(existingSyncBlock !== null && existingSyncBlock !== void 0 && existingSyncBlock.sourceURL)) {
54
- this.fetchSyncBlockSourceURL({
55
- localId: syncBlockNode.attrs.localId,
56
- resourceId: syncBlockNode.attrs.resourceId
57
- });
103
+ const data = await this.fetchSyncBlocksData([syncNode]);
104
+ if (!data || data.length === 0) {
105
+ throw new Error('Failed to fetch sync block data');
58
106
  }
59
- let data;
60
- try {
61
- data = await this.dataProvider.fetchNodesData([syncNode]);
62
- if (!data) {
63
- throw new Error('Failed to fetch sync block node data');
64
- }
65
- } catch (error) {
107
+ return data[0];
108
+ }
109
+ async fetchSyncBlocksData(syncBlockNodes) {
110
+ if (!this.dataProvider) {
111
+ throw new Error('Data provider not set');
112
+ }
113
+ const data = await this.dataProvider.fetchNodesData(syncBlockNodes);
114
+ if (!data) {
66
115
  throw new Error('Failed to fetch sync block node data');
67
116
  }
68
- const fetchSyncBlockDataResult = data[0];
69
- if (!fetchSyncBlockDataResult.error && fetchSyncBlockDataResult.data) {
70
- // only adds it to the map if it did not error out
71
- this.syncBlockCache.set(syncBlockNode.attrs.localId, {
72
- ...existingSyncBlock,
73
- ...fetchSyncBlockDataResult.data
74
- });
117
+ const resolvedData = [];
118
+ data.forEach(fetchSyncBlockDataResult => {
119
+ var _resolvedFetchSyncBlo;
120
+ if (!fetchSyncBlockDataResult.resourceId) {
121
+ return;
122
+ }
123
+ const existingSyncBlock = this.getFromCache(fetchSyncBlockDataResult.resourceId);
124
+ const resolvedFetchSyncBlockDataResult = existingSyncBlock ? resolveFetchSyncBlockDataResult(existingSyncBlock, fetchSyncBlockDataResult) : fetchSyncBlockDataResult;
125
+ this.updateCache(resolvedFetchSyncBlockDataResult);
126
+ resolvedData.push(resolvedFetchSyncBlockDataResult);
127
+
128
+ // fetch source URL if not already present
129
+ if (!((_resolvedFetchSyncBlo = resolvedFetchSyncBlockDataResult.data) !== null && _resolvedFetchSyncBlo !== void 0 && _resolvedFetchSyncBlo.sourceURL) && resolvedFetchSyncBlockDataResult.resourceId) {
130
+ this.fetchSyncBlockSourceURL(resolvedFetchSyncBlockDataResult.resourceId);
131
+ }
132
+ });
133
+ return resolvedData;
134
+ }
135
+ updateCache(syncBlock) {
136
+ const {
137
+ resourceId
138
+ } = syncBlock;
139
+ if (resourceId) {
140
+ this.syncBlockCache.set(resourceId, syncBlock);
141
+ const callbacks = this.subscriptions.get(resourceId);
142
+ if (callbacks) {
143
+ Object.values(callbacks).forEach(callback => {
144
+ callback(syncBlock);
145
+ });
146
+ }
147
+ }
148
+ }
149
+ getFromCache(resourceId) {
150
+ return this.syncBlockCache.get(resourceId);
151
+ }
152
+ deleteFromCache(resourceId) {
153
+ this.syncBlockCache.delete(resourceId);
154
+ }
155
+ subscribe(node, callback) {
156
+ // check node is a sync block, as we only support sync block subscriptions
157
+ if (node.type.name !== 'syncBlock') {
158
+ return () => {};
75
159
  }
76
- return fetchSyncBlockDataResult;
160
+ const {
161
+ resourceId,
162
+ localId
163
+ } = node.attrs;
164
+ if (!localId || !resourceId) {
165
+ return () => {};
166
+ }
167
+
168
+ // add to subscriptions map
169
+ const resourceSubscriptions = this.subscriptions.get(resourceId) || {};
170
+ this.subscriptions.set(resourceId, {
171
+ ...resourceSubscriptions,
172
+ [localId]: callback
173
+ });
174
+
175
+ // call the callback immediately if we have cached data
176
+ const cachedData = this.getFromCache(resourceId);
177
+ if (cachedData) {
178
+ callback(cachedData);
179
+ } else {
180
+ this.fetchSyncBlockData(node).catch(() => {});
181
+ }
182
+ return () => {
183
+ const resourceSubscriptions = this.subscriptions.get(resourceId);
184
+ if (resourceSubscriptions) {
185
+ delete resourceSubscriptions[localId];
186
+ if (Object.keys(resourceSubscriptions).length === 0) {
187
+ this.subscriptions.delete(resourceId);
188
+ this.deleteFromCache(resourceId);
189
+ } else {
190
+ this.subscriptions.set(resourceId, resourceSubscriptions);
191
+ }
192
+ }
193
+ };
77
194
  }
78
195
 
79
196
  /**
80
197
  * Get the URL for a sync block.
81
- * @param localId - The local ID of the sync block to get the URL for
198
+ * @param resourceId - The resource ID of the sync block
82
199
  * @returns
83
200
  */
84
- getSyncBlockURL(localId) {
85
- const syncBlock = this.syncBlockCache.get(localId);
86
- return syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.sourceURL;
201
+ getSyncBlockURL(resourceId) {
202
+ var _syncBlock$data;
203
+ const syncBlock = this.getFromCache(resourceId);
204
+ if (!syncBlock) {
205
+ return undefined;
206
+ }
207
+ return (_syncBlock$data = syncBlock.data) === null || _syncBlock$data === void 0 ? void 0 : _syncBlock$data.sourceURL;
208
+ }
209
+ destroy() {
210
+ this.syncBlockCache.clear();
211
+ this.subscriptions.clear();
212
+ this.syncBlockURLRequests.clear();
213
+ this.editorView = undefined;
214
+ this.isInitialized = false;
87
215
  }
88
216
  }
@@ -95,10 +95,22 @@ export class SourceSyncBlockStoreManager {
95
95
  if (confirmed) {
96
96
  var _this$editorView;
97
97
  (_this$editorView = this.editorView) === null || _this$editorView === void 0 ? void 0 : _this$editorView.dispatch(this.confirmationTransaction.setMeta('isConfirmedSyncBlockDeletion', true));
98
- // Need to update the BE on deletion
99
- syncBlockIds.forEach(({
100
- localId
101
- }) => this.syncBlockCache.delete(localId));
98
+ try {
99
+ if (!this.dataProvider) {
100
+ throw new Error('Data provider not set');
101
+ }
102
+ const results = await this.dataProvider.deleteNodesData(syncBlockIds.map(attrs => attrs.resourceId));
103
+ results.forEach(result => {
104
+ if (result.success) {
105
+ // Only delete when it's deleted successfully in backend
106
+ this.syncBlockCache.delete(result.resourceId);
107
+ } else {
108
+ // TODO: EDITOR-1921 - add error analytics with result.error
109
+ }
110
+ });
111
+ } catch (_error) {
112
+ // TODO: EDITOR-1921 - add error analytics
113
+ }
102
114
  }
103
115
  this.confirmationTransaction = undefined;
104
116
  }
@@ -11,6 +11,12 @@ export class SyncBlockStoreManager {
11
11
  this.referenceSyncBlockStoreManager = new ReferenceSyncBlockStoreManager(dataProvider);
12
12
  this.sourceSyncBlockStoreManager = new SourceSyncBlockStoreManager(dataProvider);
13
13
  }
14
+
15
+ /**
16
+ * Fetch sync block data for a given sync block node.
17
+ * @param syncBlockNode - The sync block node to fetch data for
18
+ * @returns The fetched sync block data result
19
+ */
14
20
  fetchSyncBlockData(syncBlockNode) {
15
21
  if (!['bodiedSyncBlock', 'syncBlock'].includes(syncBlockNode.type.name)) {
16
22
  throw new Error('Node is not a sync block');
@@ -45,16 +51,18 @@ export class SyncBlockStoreManager {
45
51
 
46
52
  /**
47
53
  * Get the URL for a sync block.
48
- * @param localId - The local ID of the sync block to get the URL for
54
+ * @param resourceId - The resource ID of the sync block to get the URL for
49
55
  * @returns
50
56
  */
51
- getSyncBlockURL(localId) {
57
+ getSyncBlockURL(resourceId) {
52
58
  // only applicable to reference sync block, for now (will be refactored further)
53
- return this.referenceSyncBlockStoreManager.getSyncBlockURL(localId);
59
+ return this.referenceSyncBlockStoreManager.getSyncBlockURL(resourceId);
54
60
  }
55
61
  setEditorView(editorView) {
56
- // only applicable to source sync block, for now (will be refactored further)
57
62
  this.sourceSyncBlockStoreManager.setEditorView(editorView);
63
+ if (editorView) {
64
+ this.referenceSyncBlockStoreManager.init(editorView);
65
+ }
58
66
  }
59
67
  isSourceBlock(node) {
60
68
  return node.type.name === 'bodiedSyncBlock';
@@ -71,6 +79,12 @@ export class SyncBlockStoreManager {
71
79
  // only applicable to source sync block, for now (will be refactored further)
72
80
  return this.sourceSyncBlockStoreManager.createSyncBlockNode();
73
81
  }
82
+ subscribeToSyncBlockData(node, callback) {
83
+ return this.referenceSyncBlockStoreManager.subscribe(node, callback);
84
+ }
85
+ refreshSubscriptions() {
86
+ this.referenceSyncBlockStoreManager.refreshSubscriptions();
87
+ }
74
88
  deleteSyncBlocksWithConfirmation(tr, syncBlockIds) {
75
89
  // only applicable to source sync block, for now (will be refactored further)
76
90
  return this.sourceSyncBlockStoreManager.deleteSyncBlocksWithConfirmation(tr, syncBlockIds);
@@ -79,4 +93,7 @@ export class SyncBlockStoreManager {
79
93
  // only applicable to source sync block, for now (will be refactored further)
80
94
  this.sourceSyncBlockStoreManager.rebaseTransaction(incomingTr, state);
81
95
  }
96
+ destroy() {
97
+ this.referenceSyncBlockStoreManager.destroy();
98
+ }
82
99
  }
@@ -11,6 +11,7 @@ const GRAPHQL_ENDPOINT = '/gateway/api/graphql';
11
11
  const GET_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_GET';
12
12
  const CREATE_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_CREATE';
13
13
  const UPDATE_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_UPDATE';
14
+ const DELETE_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_DELETE';
14
15
  /**
15
16
  * Query to get the page property by key
16
17
  * @param documentARI
@@ -116,6 +117,24 @@ const UPDATE_BLOG_QUERY = `mutation ${UPDATE_OPERATION_NAME} ($input: Confluence
116
117
  }
117
118
  }
118
119
  }`;
120
+ const DELETE_PAGE_QUERY = `mutation ${DELETE_OPERATION_NAME} ($input: ConfluenceDeletePagePropertyInput!) {
121
+ confluence {
122
+ deletePageProperty(input: $input) {
123
+ success, errors {
124
+ message
125
+ }
126
+ }
127
+ }
128
+ }`;
129
+ const DELETE_BLOG_QUERY = `mutation ${DELETE_OPERATION_NAME} ($input: ConfluenceDeleteBlogPostPropertyInput!) {
130
+ confluence {
131
+ deleteBlogPostProperty(input: $input) {
132
+ success, errors {
133
+ message
134
+ }
135
+ }
136
+ }
137
+ }`;
119
138
  export const getContentProperty = async ({
120
139
  pageId,
121
140
  key,
@@ -234,4 +253,39 @@ export const createContentProperty = async ({
234
253
  throw new Error(`Failed to create content property: ${response.statusText}`);
235
254
  }
236
255
  return await response.json();
256
+ };
257
+ export const deleteContentProperty = async ({
258
+ pageId,
259
+ cloudId,
260
+ pageType,
261
+ key
262
+ }) => {
263
+ const documentARI = getConfluencePageAri(pageId, cloudId, pageType);
264
+ const isBlog = isBlogPageType(pageType);
265
+ const bodyData = {
266
+ query: isBlog ? DELETE_BLOG_QUERY : DELETE_PAGE_QUERY,
267
+ operationName: DELETE_OPERATION_NAME,
268
+ variables: {
269
+ input: {
270
+ ...(isBlog ? {
271
+ blogPostId: documentARI
272
+ } : {
273
+ pageId: documentARI
274
+ }),
275
+ key
276
+ }
277
+ }
278
+ };
279
+ const response = await fetch(GRAPHQL_ENDPOINT, {
280
+ method: 'POST',
281
+ headers: {
282
+ ...COMMON_HEADERS,
283
+ ...AGG_HEADERS
284
+ },
285
+ body: JSON.stringify(bodyData)
286
+ });
287
+ if (!response.ok) {
288
+ throw new Error(`Failed to delete content property: ${response.statusText}`);
289
+ }
290
+ return await response.json();
237
291
  };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Merges two FetchSyncBlockDataResult objects,
3
+ * currently it only preserves the sourceURL from the old result,
4
+ * but this can be extended in the future to preserve other fields and resolve conflicts as needed.
5
+ * e.g. compare timestamps or version numbers to determine which data is more recent.
6
+ *
7
+ * @param oldResult - The existing FetchSyncBlockDataResult object.
8
+ * @param newResult - The new FetchSyncBlockDataResult object to merge.
9
+ * @returns A merged FetchSyncBlockDataResult object.
10
+ */
11
+ export const resolveFetchSyncBlockDataResult = (oldResult, newResult) => {
12
+ var _newResult$data, _oldResult$data;
13
+ // if the old result has no data, we simple return the new result
14
+ if (!oldResult.data) {
15
+ return newResult;
16
+ } else if (!newResult.data) {
17
+ // if the new result has no data, we simply return the old result
18
+ // TODO: EDITOR-2533 - handle this case based on the error type and whether we should keep old data or not
19
+ return oldResult;
20
+ }
21
+
22
+ // otherwise, we merge the two results, preserving the sourceURL from the old result if it exists
23
+ return {
24
+ ...newResult,
25
+ data: {
26
+ ...newResult.data,
27
+ sourceURL: ((_newResult$data = newResult.data) === null || _newResult$data === void 0 ? void 0 : _newResult$data.sourceURL) || ((_oldResult$data = oldResult.data) === null || _oldResult$data === void 0 ? void 0 : _oldResult$data.sourceURL) || undefined
28
+ }
29
+ };
30
+ };