@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.
- package/CHANGELOG.md +14 -0
- package/dist/cjs/hooks/useFetchSyncBlockData.js +5 -30
- package/dist/cjs/providers/confluence/confluenceContentAPI.js +50 -0
- package/dist/cjs/providers/in-memory/inMemory.js +7 -0
- package/dist/cjs/providers/syncBlockProvider.js +75 -19
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +319 -63
- package/dist/cjs/store-manager/sourceSyncBlockStoreManager.js +35 -11
- package/dist/cjs/store-manager/syncBlockStoreManager.js +27 -4
- package/dist/cjs/utils/contentProperty.js +54 -1
- package/dist/cjs/utils/mergeFetchSyncBlockDataResult.js +38 -0
- package/dist/es2019/hooks/useFetchSyncBlockData.js +6 -31
- package/dist/es2019/providers/confluence/confluenceContentAPI.js +31 -1
- package/dist/es2019/providers/in-memory/inMemory.js +7 -0
- package/dist/es2019/providers/syncBlockProvider.js +31 -3
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +172 -44
- package/dist/es2019/store-manager/sourceSyncBlockStoreManager.js +16 -4
- package/dist/es2019/store-manager/syncBlockStoreManager.js +21 -4
- package/dist/es2019/utils/contentProperty.js +54 -0
- package/dist/es2019/utils/mergeFetchSyncBlockDataResult.js +30 -0
- package/dist/esm/hooks/useFetchSyncBlockData.js +6 -31
- package/dist/esm/providers/confluence/confluenceContentAPI.js +51 -1
- package/dist/esm/providers/in-memory/inMemory.js +7 -0
- package/dist/esm/providers/syncBlockProvider.js +73 -17
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +320 -64
- package/dist/esm/store-manager/sourceSyncBlockStoreManager.js +35 -11
- package/dist/esm/store-manager/syncBlockStoreManager.js +27 -4
- package/dist/esm/utils/contentProperty.js +53 -0
- package/dist/esm/utils/mergeFetchSyncBlockDataResult.js +31 -0
- package/dist/types/common/schema.d.ts +1 -1
- package/dist/types/providers/confluence/confluenceContentAPI.d.ts +2 -1
- package/dist/types/providers/syncBlockProvider.d.ts +3 -2
- package/dist/types/providers/types.d.ts +8 -0
- package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +24 -8
- package/dist/types/store-manager/sourceSyncBlockStoreManager.d.ts +1 -1
- package/dist/types/store-manager/syncBlockStoreManager.d.ts +12 -4
- package/dist/types/utils/contentProperty.d.ts +35 -0
- package/dist/types/utils/mergeFetchSyncBlockDataResult.d.ts +12 -0
- package/dist/types-ts4.5/common/schema.d.ts +1 -1
- package/dist/types-ts4.5/providers/confluence/confluenceContentAPI.d.ts +2 -1
- package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +3 -2
- package/dist/types-ts4.5/providers/types.d.ts +8 -0
- package/dist/types-ts4.5/store-manager/referenceSyncBlockStoreManager.d.ts +24 -8
- package/dist/types-ts4.5/store-manager/sourceSyncBlockStoreManager.d.ts +1 -1
- package/dist/types-ts4.5/store-manager/syncBlockStoreManager.d.ts +12 -4
- package/dist/types-ts4.5/utils/contentProperty.d.ts +35 -0
- package/dist/types-ts4.5/utils/mergeFetchSyncBlockDataResult.d.ts +12 -0
- 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 {
|
|
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
|
-
|
|
33
|
-
|
|
6
|
+
const unsubscribe = manager.subscribeToSyncBlockData(syncBlockNode, data => {
|
|
7
|
+
setFetchSyncBlockDataResult(data);
|
|
8
|
+
});
|
|
34
9
|
return () => {
|
|
35
|
-
|
|
10
|
+
unsubscribe();
|
|
36
11
|
};
|
|
37
|
-
}, [
|
|
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
|
-
|
|
21
|
-
|
|
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
|
|
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
|
-
* @
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
34
|
-
this.syncBlockURLRequests.set(
|
|
35
|
-
this.dataProvider.retrieveSyncBlockSourceUrl(createSyncBlockNode(
|
|
36
|
-
const existingSyncBlock = this.
|
|
37
|
-
if (existingSyncBlock) {
|
|
38
|
-
existingSyncBlock.
|
|
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(
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
|
198
|
+
* @param resourceId - The resource ID of the sync block
|
|
82
199
|
* @returns
|
|
83
200
|
*/
|
|
84
|
-
getSyncBlockURL(
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
54
|
+
* @param resourceId - The resource ID of the sync block to get the URL for
|
|
49
55
|
* @returns
|
|
50
56
|
*/
|
|
51
|
-
getSyncBlockURL(
|
|
57
|
+
getSyncBlockURL(resourceId) {
|
|
52
58
|
// only applicable to reference sync block, for now (will be refactored further)
|
|
53
|
-
return this.referenceSyncBlockStoreManager.getSyncBlockURL(
|
|
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
|
+
};
|