@atlaskit/editor-plugin-synced-block 1.0.0 → 2.0.0
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 +5 -21
- package/afm-cc/tsconfig.json +0 -15
- package/afm-dev-agents/tsconfig.json +0 -15
- package/afm-jira/tsconfig.json +0 -15
- package/afm-passionfruit/tsconfig.json +0 -15
- package/afm-post-office/tsconfig.json +0 -15
- package/afm-rovo-extension/tsconfig.json +0 -15
- package/afm-townsquare/tsconfig.json +0 -15
- package/dist/cjs/nodeviews/syncedBlock.js +20 -0
- package/dist/cjs/pm-plugins/main.js +0 -21
- package/dist/cjs/syncedBlockPlugin.js +9 -5
- package/dist/cjs/ui/floating-toolbar.js +39 -0
- package/dist/es2019/nodeviews/syncedBlock.js +10 -0
- package/dist/es2019/pm-plugins/main.js +0 -24
- package/dist/es2019/syncedBlockPlugin.js +18 -15
- package/dist/es2019/ui/floating-toolbar.js +31 -0
- package/dist/esm/nodeviews/syncedBlock.js +13 -0
- package/dist/esm/pm-plugins/main.js +0 -21
- package/dist/esm/syncedBlockPlugin.js +9 -4
- package/dist/esm/ui/floating-toolbar.js +32 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/nodeviews/syncedBlock.d.ts +11 -0
- package/dist/types/ui/floating-toolbar.d.ts +2 -0
- package/dist/types-ts4.5/index.d.ts +0 -1
- package/dist/types-ts4.5/nodeviews/syncedBlock.d.ts +11 -0
- package/dist/types-ts4.5/ui/floating-toolbar.d.ts +2 -0
- package/package.json +3 -9
- package/SyncedBlock/package.json +0 -15
- package/dist/cjs/pm-plugins/SyncClient.js +0 -167
- package/dist/cjs/pm-plugins/utils.js +0 -19
- package/dist/cjs/types/index.js +0 -1
- package/dist/cjs/ui/extensions/synced-block/components/GlobalStyles.js +0 -25
- package/dist/cjs/ui/extensions/synced-block/components/SyncedBlockLiveView.js +0 -25
- package/dist/cjs/ui/extensions/synced-block/components/SyncedBlockRenderer.js +0 -25
- package/dist/cjs/ui/extensions/synced-block/constants.js +0 -32
- package/dist/cjs/ui/extensions/synced-block/getSyncedBlockExtensionProvider.js +0 -11
- package/dist/cjs/ui/extensions/synced-block/hooks/useLiveSyncedBlockContent.js +0 -29
- package/dist/cjs/ui/extensions/synced-block/hooks/usePollContentProperty.js +0 -121
- package/dist/cjs/ui/extensions/synced-block/index.js +0 -19
- package/dist/cjs/ui/extensions/synced-block/manifest.js +0 -281
- package/dist/cjs/ui/extensions/synced-block/utils/ari.js +0 -29
- package/dist/cjs/ui/extensions/synced-block/utils/content-property.js +0 -159
- package/dist/cjs/ui/extensions/synced-block/utils/synced-block.js +0 -65
- package/dist/es2019/pm-plugins/SyncClient.js +0 -102
- package/dist/es2019/pm-plugins/utils.js +0 -13
- package/dist/es2019/types/index.js +0 -0
- package/dist/es2019/ui/extensions/synced-block/components/GlobalStyles.js +0 -18
- package/dist/es2019/ui/extensions/synced-block/components/SyncedBlockLiveView.js +0 -19
- package/dist/es2019/ui/extensions/synced-block/components/SyncedBlockRenderer.js +0 -19
- package/dist/es2019/ui/extensions/synced-block/constants.js +0 -26
- package/dist/es2019/ui/extensions/synced-block/getSyncedBlockExtensionProvider.js +0 -5
- package/dist/es2019/ui/extensions/synced-block/hooks/useLiveSyncedBlockContent.js +0 -24
- package/dist/es2019/ui/extensions/synced-block/hooks/usePollContentProperty.js +0 -107
- package/dist/es2019/ui/extensions/synced-block/index.js +0 -5
- package/dist/es2019/ui/extensions/synced-block/manifest.js +0 -172
- package/dist/es2019/ui/extensions/synced-block/utils/ari.js +0 -19
- package/dist/es2019/ui/extensions/synced-block/utils/content-property.js +0 -108
- package/dist/es2019/ui/extensions/synced-block/utils/synced-block.js +0 -57
- package/dist/esm/pm-plugins/SyncClient.js +0 -160
- package/dist/esm/pm-plugins/utils.js +0 -13
- package/dist/esm/types/index.js +0 -0
- package/dist/esm/ui/extensions/synced-block/components/GlobalStyles.js +0 -18
- package/dist/esm/ui/extensions/synced-block/components/SyncedBlockLiveView.js +0 -18
- package/dist/esm/ui/extensions/synced-block/components/SyncedBlockRenderer.js +0 -18
- package/dist/esm/ui/extensions/synced-block/constants.js +0 -26
- package/dist/esm/ui/extensions/synced-block/getSyncedBlockExtensionProvider.js +0 -5
- package/dist/esm/ui/extensions/synced-block/hooks/useLiveSyncedBlockContent.js +0 -23
- package/dist/esm/ui/extensions/synced-block/hooks/usePollContentProperty.js +0 -114
- package/dist/esm/ui/extensions/synced-block/index.js +0 -5
- package/dist/esm/ui/extensions/synced-block/manifest.js +0 -274
- package/dist/esm/ui/extensions/synced-block/utils/ari.js +0 -23
- package/dist/esm/ui/extensions/synced-block/utils/content-property.js +0 -153
- package/dist/esm/ui/extensions/synced-block/utils/synced-block.js +0 -58
- package/dist/types/pm-plugins/SyncClient.d.ts +0 -14
- package/dist/types/pm-plugins/utils.d.ts +0 -5
- package/dist/types/types/index.d.ts +0 -3
- package/dist/types/ui/extensions/synced-block/components/GlobalStyles.d.ts +0 -6
- package/dist/types/ui/extensions/synced-block/components/SyncedBlockLiveView.d.ts +0 -7
- package/dist/types/ui/extensions/synced-block/components/SyncedBlockRenderer.d.ts +0 -7
- package/dist/types/ui/extensions/synced-block/constants.d.ts +0 -8
- package/dist/types/ui/extensions/synced-block/getSyncedBlockExtensionProvider.d.ts +0 -3
- package/dist/types/ui/extensions/synced-block/hooks/useLiveSyncedBlockContent.d.ts +0 -6
- package/dist/types/ui/extensions/synced-block/hooks/usePollContentProperty.d.ts +0 -7
- package/dist/types/ui/extensions/synced-block/index.d.ts +0 -2
- package/dist/types/ui/extensions/synced-block/manifest.d.ts +0 -3
- package/dist/types/ui/extensions/synced-block/utils/ari.d.ts +0 -4
- package/dist/types/ui/extensions/synced-block/utils/content-property.d.ts +0 -33
- package/dist/types/ui/extensions/synced-block/utils/synced-block.d.ts +0 -24
- package/dist/types-ts4.5/pm-plugins/SyncClient.d.ts +0 -14
- package/dist/types-ts4.5/pm-plugins/utils.d.ts +0 -5
- package/dist/types-ts4.5/types/index.d.ts +0 -3
- package/dist/types-ts4.5/ui/extensions/synced-block/components/GlobalStyles.d.ts +0 -6
- package/dist/types-ts4.5/ui/extensions/synced-block/components/SyncedBlockLiveView.d.ts +0 -7
- package/dist/types-ts4.5/ui/extensions/synced-block/components/SyncedBlockRenderer.d.ts +0 -7
- package/dist/types-ts4.5/ui/extensions/synced-block/constants.d.ts +0 -8
- package/dist/types-ts4.5/ui/extensions/synced-block/getSyncedBlockExtensionProvider.d.ts +0 -3
- package/dist/types-ts4.5/ui/extensions/synced-block/hooks/useLiveSyncedBlockContent.d.ts +0 -6
- package/dist/types-ts4.5/ui/extensions/synced-block/hooks/usePollContentProperty.d.ts +0 -7
- package/dist/types-ts4.5/ui/extensions/synced-block/index.d.ts +0 -2
- package/dist/types-ts4.5/ui/extensions/synced-block/manifest.d.ts +0 -3
- package/dist/types-ts4.5/ui/extensions/synced-block/utils/ari.d.ts +0 -4
- package/dist/types-ts4.5/ui/extensions/synced-block/utils/content-property.d.ts +0 -33
- package/dist/types-ts4.5/ui/extensions/synced-block/utils/synced-block.d.ts +0 -24
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useLiveSyncedBlockContent } from '../hooks/useLiveSyncedBlockContent';
|
|
3
|
-
import SyncedBlockRenderer from './SyncedBlockRenderer';
|
|
4
|
-
const SyncedBlockLiveView = ({
|
|
5
|
-
sourceDocumentAri,
|
|
6
|
-
contentAri
|
|
7
|
-
}) => {
|
|
8
|
-
const syncedBlockContent = useLiveSyncedBlockContent({
|
|
9
|
-
sourceDocumentAri,
|
|
10
|
-
contentAri
|
|
11
|
-
});
|
|
12
|
-
if (!syncedBlockContent) {
|
|
13
|
-
return /*#__PURE__*/React.createElement("div", null, "Loading...");
|
|
14
|
-
}
|
|
15
|
-
return /*#__PURE__*/React.createElement(SyncedBlockRenderer, {
|
|
16
|
-
syncedBlockContent: syncedBlockContent
|
|
17
|
-
});
|
|
18
|
-
};
|
|
19
|
-
export default SyncedBlockLiveView;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { ReactRenderer } from '@atlaskit/renderer';
|
|
3
|
-
import { RendererActionsContext } from '@atlaskit/renderer/actions';
|
|
4
|
-
const SyncedBlockRenderer = ({
|
|
5
|
-
syncedBlockContent
|
|
6
|
-
}) => {
|
|
7
|
-
return /*#__PURE__*/React.createElement(RendererActionsContext, null, /*#__PURE__*/React.createElement(ReactRenderer, {
|
|
8
|
-
adfStage: "stage0"
|
|
9
|
-
// @ts-ignore
|
|
10
|
-
,
|
|
11
|
-
document: {
|
|
12
|
-
type: 'doc',
|
|
13
|
-
version: 1,
|
|
14
|
-
content: syncedBlockContent.adf.content
|
|
15
|
-
},
|
|
16
|
-
appearance: "full-page"
|
|
17
|
-
}));
|
|
18
|
-
};
|
|
19
|
-
export default SyncedBlockRenderer;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// hello.atlassian.net cloud id
|
|
2
|
-
const HELLO_CLOUD_ID = 'a436116f-02ce-4520-8fbb-7301462a1674';
|
|
3
|
-
|
|
4
|
-
// spike page https://hello.atlassian.net/wiki/spaces/~7120208ef57ce4d614485e876489301a16b906/pages/5626233808
|
|
5
|
-
const TEST_PAGE_ID = '5626233808';
|
|
6
|
-
export const getPageId = () => {
|
|
7
|
-
var _window$location$href, _window$location$href2, _window$location$path;
|
|
8
|
-
return (
|
|
9
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
10
|
-
((_window$location$href = window.location.href.match(/pageId=(\d+)/)) === null || _window$location$href === void 0 ? void 0 : _window$location$href[1]) || ( // eslint-disable-next-line require-unicode-regexp
|
|
11
|
-
(_window$location$href2 = window.location.href.match(/pages\/edit-v2\/(\d+)/)) === null || _window$location$href2 === void 0 ? void 0 : _window$location$href2[1]) || ( // eslint-disable-next-line require-unicode-regexp
|
|
12
|
-
(_window$location$path = window.location.pathname.match(/pages\/(\d+)/)) === null || _window$location$path === void 0 ? void 0 : _window$location$path[1]) ||
|
|
13
|
-
// view page or live doc
|
|
14
|
-
TEST_PAGE_ID
|
|
15
|
-
);
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* This by no means is a stable way to get the cloud id, but it works for now.
|
|
19
|
-
* We should switch passing the cloud id from Confluence to a Editor plugin,
|
|
20
|
-
* for instance the user preferences plugin would have a seperate place for user and cloud info
|
|
21
|
-
* @returns the cloud id from the initial state
|
|
22
|
-
*/
|
|
23
|
-
export const getCloudId = () => {
|
|
24
|
-
var _INITIAL_STATE__, _INITIAL_STATE__$meta;
|
|
25
|
-
return ((_INITIAL_STATE__ = window.__INITIAL_STATE__) === null || _INITIAL_STATE__ === void 0 ? void 0 : (_INITIAL_STATE__$meta = _INITIAL_STATE__.meta) === null || _INITIAL_STATE__$meta === void 0 ? void 0 : _INITIAL_STATE__$meta['cloud-id']) || HELLO_CLOUD_ID;
|
|
26
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { DefaultExtensionProvider } from '@atlaskit/editor-common/extensions';
|
|
2
|
-
import { getSyncedBlockManifest } from './manifest';
|
|
3
|
-
export const getSyncedBlockExtensionProvider = schema => {
|
|
4
|
-
return new DefaultExtensionProvider([getSyncedBlockManifest(schema)]);
|
|
5
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { usePollContentProperty } from '../hooks/usePollContentProperty';
|
|
3
|
-
import { parseSyncedBlockContentPropertyValue } from '../utils/synced-block';
|
|
4
|
-
export const useLiveSyncedBlockContent = ({
|
|
5
|
-
sourceDocumentAri,
|
|
6
|
-
contentAri
|
|
7
|
-
}) => {
|
|
8
|
-
const contentProperty = usePollContentProperty({
|
|
9
|
-
sourceDocumentAri,
|
|
10
|
-
contentAri
|
|
11
|
-
});
|
|
12
|
-
return useMemo(() => {
|
|
13
|
-
if (!contentProperty) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
return parseSyncedBlockContentPropertyValue(contentProperty.value);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
// eslint-disable-next-line no-console
|
|
20
|
-
console.error('Failed to extract synced block content:', error);
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}, [contentProperty]);
|
|
24
|
-
};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { getContentPropertyIdFromAri, getPageIdFromAri } from '../utils/ari';
|
|
3
|
-
import { getContentProperty } from '../utils/content-property';
|
|
4
|
-
const POLLING_INTERVAL = 1000;
|
|
5
|
-
const cache = new Map();
|
|
6
|
-
const inFlightRequests = new Map();
|
|
7
|
-
const subscribers = new Map();
|
|
8
|
-
const pollingTimeouts = new Map();
|
|
9
|
-
const lastRequestTimes = new Map();
|
|
10
|
-
const getRequestKey = (pageId, contentPropertyId) => `${pageId}:${contentPropertyId}`;
|
|
11
|
-
const fetchContentPropertyWithDedup = (pageId, contentPropertyId) => {
|
|
12
|
-
const requestKey = getRequestKey(pageId, contentPropertyId);
|
|
13
|
-
lastRequestTimes.set(requestKey, Date.now());
|
|
14
|
-
const inFlightRequest = inFlightRequests.get(requestKey);
|
|
15
|
-
if (inFlightRequest) {
|
|
16
|
-
return inFlightRequest;
|
|
17
|
-
}
|
|
18
|
-
const requestPromise = getContentProperty({
|
|
19
|
-
pageId,
|
|
20
|
-
contentPropertyId
|
|
21
|
-
}).then(result => {
|
|
22
|
-
cache.set(requestKey, result);
|
|
23
|
-
const subscribersForKey = subscribers.get(requestKey);
|
|
24
|
-
if (subscribersForKey) {
|
|
25
|
-
subscribersForKey.forEach(callback => callback(result));
|
|
26
|
-
}
|
|
27
|
-
inFlightRequests.delete(requestKey);
|
|
28
|
-
if (subscribersForKey && subscribersForKey.size > 0) {
|
|
29
|
-
scheduleNextPoll(pageId, contentPropertyId);
|
|
30
|
-
}
|
|
31
|
-
return result;
|
|
32
|
-
}).catch(error => {
|
|
33
|
-
inFlightRequests.delete(requestKey);
|
|
34
|
-
const subscribersForKey = subscribers.get(requestKey);
|
|
35
|
-
if (subscribersForKey && subscribersForKey.size > 0) {
|
|
36
|
-
scheduleNextPoll(pageId, contentPropertyId);
|
|
37
|
-
}
|
|
38
|
-
throw error;
|
|
39
|
-
});
|
|
40
|
-
inFlightRequests.set(requestKey, requestPromise);
|
|
41
|
-
return requestPromise;
|
|
42
|
-
};
|
|
43
|
-
const scheduleNextPoll = (pageId, contentPropertyId) => {
|
|
44
|
-
const requestKey = getRequestKey(pageId, contentPropertyId);
|
|
45
|
-
const existingTimeout = pollingTimeouts.get(requestKey);
|
|
46
|
-
if (existingTimeout) {
|
|
47
|
-
clearTimeout(existingTimeout);
|
|
48
|
-
}
|
|
49
|
-
const lastRequestTime = lastRequestTimes.get(requestKey) || 0;
|
|
50
|
-
const timeElapsed = Date.now() - lastRequestTime;
|
|
51
|
-
const delay = Math.max(100, POLLING_INTERVAL - timeElapsed);
|
|
52
|
-
const timeout = setTimeout(() => {
|
|
53
|
-
const subscribersForKey = subscribers.get(requestKey);
|
|
54
|
-
if (subscribersForKey && subscribersForKey.size > 0) {
|
|
55
|
-
fetchContentPropertyWithDedup(pageId, contentPropertyId).catch(error => {
|
|
56
|
-
// eslint-disable-next-line no-console
|
|
57
|
-
console.error('Failed to fetch content property:', error);
|
|
58
|
-
});
|
|
59
|
-
} else {
|
|
60
|
-
pollingTimeouts.delete(requestKey);
|
|
61
|
-
}
|
|
62
|
-
}, delay);
|
|
63
|
-
pollingTimeouts.set(requestKey, timeout);
|
|
64
|
-
};
|
|
65
|
-
export const usePollContentProperty = ({
|
|
66
|
-
sourceDocumentAri,
|
|
67
|
-
contentAri
|
|
68
|
-
}) => {
|
|
69
|
-
const [contentProperty, setContentProperty] = useState();
|
|
70
|
-
const initializedRef = useRef(false);
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
const pageId = getPageIdFromAri(sourceDocumentAri);
|
|
73
|
-
const contentPropertyId = getContentPropertyIdFromAri(contentAri);
|
|
74
|
-
const requestKey = getRequestKey(pageId, contentPropertyId);
|
|
75
|
-
const subscribersForKey = subscribers.get(requestKey) || new Set();
|
|
76
|
-
if (!subscribers.has(requestKey)) {
|
|
77
|
-
subscribers.set(requestKey, subscribersForKey);
|
|
78
|
-
}
|
|
79
|
-
subscribersForKey.add(setContentProperty);
|
|
80
|
-
const cachedValue = cache.get(requestKey);
|
|
81
|
-
if (cachedValue) {
|
|
82
|
-
setContentProperty(cachedValue);
|
|
83
|
-
}
|
|
84
|
-
if (subscribersForKey.size === 1 || !initializedRef.current) {
|
|
85
|
-
initializedRef.current = true;
|
|
86
|
-
fetchContentPropertyWithDedup(pageId, contentPropertyId).catch(error => {
|
|
87
|
-
// eslint-disable-next-line no-console
|
|
88
|
-
console.error('Failed to fetch content property:', error);
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return () => {
|
|
92
|
-
subscribersForKey.delete(setContentProperty);
|
|
93
|
-
if (subscribersForKey.size === 0) {
|
|
94
|
-
subscribers.delete(requestKey);
|
|
95
|
-
const existingTimeout = pollingTimeouts.get(requestKey);
|
|
96
|
-
if (existingTimeout) {
|
|
97
|
-
clearTimeout(existingTimeout);
|
|
98
|
-
pollingTimeouts.delete(requestKey);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
}, [sourceDocumentAri, contentAri]);
|
|
103
|
-
const pageId = getPageIdFromAri(sourceDocumentAri);
|
|
104
|
-
const contentPropertyId = getContentPropertyIdFromAri(contentAri);
|
|
105
|
-
const requestKey = getRequestKey(pageId, contentPropertyId);
|
|
106
|
-
return contentProperty || cache.get(requestKey);
|
|
107
|
-
};
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { copyHTMLToClipboard } from '@atlaskit/editor-common/clipboard';
|
|
3
|
-
import { DOMSerializer, Fragment, Mark } from '@atlaskit/editor-prosemirror/model';
|
|
4
|
-
import SmartLinkIcon from '@atlaskit/icon/core/smart-link';
|
|
5
|
-
import SyncedBlockLiveView from './components/SyncedBlockLiveView';
|
|
6
|
-
import { getPageId } from './constants';
|
|
7
|
-
import { getConfluencePageAri, getContentPropertyAri } from './utils/ari';
|
|
8
|
-
import { createContentProperty } from './utils/content-property';
|
|
9
|
-
import { SYNCED_BLOCK_EXTENSION_KEY, SYNCED_BLOCK_EXTENSION_TYPE, SYNCED_BLOCK_REFERENCE_KEY, SYNCED_BLOCK_REFERENCE_NODE, SYNCED_BLOCK_SOURCE_KEY, SYNCED_BLOCK_SOURCE_NODE, getDefaultSyncedBlockContent, isSyncedBlockAttributes, stringifySyncedBlockContentPropertyValue } from './utils/synced-block';
|
|
10
|
-
const getRandomId = () => {
|
|
11
|
-
if (!globalThis.crypto || typeof globalThis.crypto.randomUUID !== 'function') {
|
|
12
|
-
return new Date().toISOString();
|
|
13
|
-
}
|
|
14
|
-
return globalThis.crypto.randomUUID();
|
|
15
|
-
};
|
|
16
|
-
const copyToClipboard = (adf, schema) => {
|
|
17
|
-
if (!schema) {
|
|
18
|
-
throw new Error('copyToClipboard(): Schema is required.');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Validate the given ADF
|
|
22
|
-
const nodeType = schema.nodes[adf.type];
|
|
23
|
-
if (!nodeType) {
|
|
24
|
-
throw new Error(`copyToClipboard(): Invalid ADF type '${adf.type}'.`);
|
|
25
|
-
}
|
|
26
|
-
const fragment = Fragment.fromJSON(schema, adf.content);
|
|
27
|
-
const marks = (adf.marks || []).map(markEntity => Mark.fromJSON(schema, markEntity));
|
|
28
|
-
const newNode = nodeType === null || nodeType === void 0 ? void 0 : nodeType.createChecked(adf.attrs, fragment, marks);
|
|
29
|
-
if (!newNode) {
|
|
30
|
-
throw new Error('copyToClipboard(): Could not create a node for given ADFEntity.');
|
|
31
|
-
}
|
|
32
|
-
const domNode = DOMSerializer.fromSchema(schema).serializeNode(newNode);
|
|
33
|
-
const div = document.createElement('div');
|
|
34
|
-
div.appendChild(domNode);
|
|
35
|
-
copyHTMLToClipboard(div);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Remaining tasks
|
|
39
|
-
// - Better location for content sync implementation – currently done in SyncedBlockSource renderer which won't work in editor
|
|
40
|
-
// - Could implement an editor plugin to do this, if there's no native way to do it with extensions
|
|
41
|
-
// - Implement separate content property for storage of the metadata of a synced block, separate from the content
|
|
42
|
-
// - Update polling to use the metadata content property, then if metadata updated, fetch the content property
|
|
43
|
-
// - Investigate re-rendering of SyncedBlockReference in editor on every document change (is this just atlaskit behavior?)
|
|
44
|
-
// - On copy of the synced block, transform into a reference
|
|
45
|
-
// - Move implementation into Confluence and test in branch environment
|
|
46
|
-
// - Dealing with orphaned synced block content properties data
|
|
47
|
-
// - Getting current page id in editor context and the cloud id
|
|
48
|
-
// - Explore hiding the frame for the extension in the editor, currently using `__hideFrame: true` and commented code to enable
|
|
49
|
-
export const getSyncedBlockManifest = schema => ({
|
|
50
|
-
title: 'Synced Block',
|
|
51
|
-
type: SYNCED_BLOCK_EXTENSION_TYPE,
|
|
52
|
-
key: SYNCED_BLOCK_EXTENSION_KEY,
|
|
53
|
-
description: 'Synced block spike',
|
|
54
|
-
icons: {
|
|
55
|
-
// Ignored via go/ees005
|
|
56
|
-
// eslint-disable-next-line require-await
|
|
57
|
-
'48': async () => () => /*#__PURE__*/React.createElement(SmartLinkIcon, {
|
|
58
|
-
label: "Synced Block",
|
|
59
|
-
size: "medium"
|
|
60
|
-
})
|
|
61
|
-
},
|
|
62
|
-
modules: {
|
|
63
|
-
quickInsert: [{
|
|
64
|
-
key: 'quick-insert-synced-block-source',
|
|
65
|
-
action: async _api => {
|
|
66
|
-
const contentPropertyKey = `synced-block-` + getRandomId();
|
|
67
|
-
const content = getDefaultSyncedBlockContent();
|
|
68
|
-
const value = stringifySyncedBlockContentPropertyValue({
|
|
69
|
-
adf: content
|
|
70
|
-
});
|
|
71
|
-
const contentProperty = await createContentProperty({
|
|
72
|
-
pageId: getPageId(),
|
|
73
|
-
key: contentPropertyKey,
|
|
74
|
-
value
|
|
75
|
-
});
|
|
76
|
-
const attributes = {
|
|
77
|
-
extensionType: SYNCED_BLOCK_EXTENSION_TYPE,
|
|
78
|
-
extensionKey: SYNCED_BLOCK_SOURCE_KEY,
|
|
79
|
-
parameters: {
|
|
80
|
-
sourceDocumentAri: getConfluencePageAri(getPageId()),
|
|
81
|
-
contentAri: getContentPropertyAri(contentProperty.id),
|
|
82
|
-
contentPropertyKey
|
|
83
|
-
},
|
|
84
|
-
localId: 'testId'
|
|
85
|
-
};
|
|
86
|
-
content.attrs = attributes;
|
|
87
|
-
return content;
|
|
88
|
-
}
|
|
89
|
-
}],
|
|
90
|
-
nodes: {
|
|
91
|
-
[SYNCED_BLOCK_SOURCE_NODE]: {
|
|
92
|
-
type: 'bodiedExtension',
|
|
93
|
-
// Ignored via go/ees005
|
|
94
|
-
// eslint-disable-next-line require-await
|
|
95
|
-
render: async () => props => {
|
|
96
|
-
if (!isSyncedBlockAttributes(props.node)) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
const {
|
|
100
|
-
sourceDocumentAri,
|
|
101
|
-
contentAri
|
|
102
|
-
} = props.node.parameters;
|
|
103
|
-
return /*#__PURE__*/React.createElement(SyncedBlockLiveView, {
|
|
104
|
-
sourceDocumentAri: sourceDocumentAri,
|
|
105
|
-
contentAri: contentAri
|
|
106
|
-
});
|
|
107
|
-
},
|
|
108
|
-
// @ts-expect-error
|
|
109
|
-
__hideFrame: true
|
|
110
|
-
},
|
|
111
|
-
[SYNCED_BLOCK_REFERENCE_NODE]: {
|
|
112
|
-
type: 'extension',
|
|
113
|
-
// Ignored via go/ees005
|
|
114
|
-
// eslint-disable-next-line require-await
|
|
115
|
-
render: async () => props => {
|
|
116
|
-
if (!isSyncedBlockAttributes(props.node)) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
const {
|
|
120
|
-
sourceDocumentAri,
|
|
121
|
-
contentAri
|
|
122
|
-
} = props.node.parameters;
|
|
123
|
-
return /*#__PURE__*/React.createElement(SyncedBlockLiveView, {
|
|
124
|
-
sourceDocumentAri: sourceDocumentAri,
|
|
125
|
-
contentAri: contentAri
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
// @ts-expect-error
|
|
129
|
-
__hideFrame: true
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
contextualToolbars: [{
|
|
133
|
-
context: {
|
|
134
|
-
type: 'extension',
|
|
135
|
-
nodeType: 'bodiedExtension',
|
|
136
|
-
extensionType: SYNCED_BLOCK_EXTENSION_TYPE,
|
|
137
|
-
extensionKey: SYNCED_BLOCK_SOURCE_KEY
|
|
138
|
-
},
|
|
139
|
-
toolbarItems: [{
|
|
140
|
-
key: 'toolbar-item-key',
|
|
141
|
-
label: 'Referenece',
|
|
142
|
-
display: 'icon',
|
|
143
|
-
tooltip: 'Copy reference to clipboard',
|
|
144
|
-
// Ignored via go/ees005
|
|
145
|
-
// eslint-disable-next-line require-await
|
|
146
|
-
icon: async () => () => /*#__PURE__*/React.createElement(SmartLinkIcon, {
|
|
147
|
-
label: "Synced Block",
|
|
148
|
-
size: "medium"
|
|
149
|
-
}),
|
|
150
|
-
// Ignored via go/ees005
|
|
151
|
-
// eslint-disable-next-line require-await
|
|
152
|
-
action: async contextNode => {
|
|
153
|
-
try {
|
|
154
|
-
var _contextNode$attrs, _contextNode$attrs$pa, _contextNode$attrs2, _contextNode$attrs2$p;
|
|
155
|
-
copyToClipboard({
|
|
156
|
-
type: 'extension',
|
|
157
|
-
attrs: {
|
|
158
|
-
extensionType: SYNCED_BLOCK_EXTENSION_TYPE,
|
|
159
|
-
extensionKey: SYNCED_BLOCK_REFERENCE_KEY,
|
|
160
|
-
parameters: {
|
|
161
|
-
sourceDocumentAri: (_contextNode$attrs = contextNode.attrs) === null || _contextNode$attrs === void 0 ? void 0 : (_contextNode$attrs$pa = _contextNode$attrs.parameters) === null || _contextNode$attrs$pa === void 0 ? void 0 : _contextNode$attrs$pa.sourceDocumentAri,
|
|
162
|
-
contentAri: (_contextNode$attrs2 = contextNode.attrs) === null || _contextNode$attrs2 === void 0 ? void 0 : (_contextNode$attrs2$p = _contextNode$attrs2.parameters) === null || _contextNode$attrs2$p === void 0 ? void 0 : _contextNode$attrs2$p.contentAri
|
|
163
|
-
},
|
|
164
|
-
localId: 'testId'
|
|
165
|
-
}
|
|
166
|
-
}, schema);
|
|
167
|
-
} catch (e) {}
|
|
168
|
-
}
|
|
169
|
-
}]
|
|
170
|
-
}]
|
|
171
|
-
}
|
|
172
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { getCloudId } from '../constants';
|
|
2
|
-
export const getConfluencePageAri = pageId => `ari:cloud:confluence:${getCloudId()}:page/${pageId}`;
|
|
3
|
-
export const getPageIdFromAri = ari => {
|
|
4
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
5
|
-
const match = ari.match(/ari:cloud:confluence:[^:]+:page\/(\d+)/);
|
|
6
|
-
if (match) {
|
|
7
|
-
return match[1];
|
|
8
|
-
}
|
|
9
|
-
throw new Error(`Invalid page ARI: ${ari}`);
|
|
10
|
-
};
|
|
11
|
-
export const getContentPropertyAri = contentPropertyId => `ari:cloud:confluence:${getCloudId()}:content/${contentPropertyId}`;
|
|
12
|
-
export const getContentPropertyIdFromAri = ari => {
|
|
13
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
14
|
-
const match = ari.match(/ari:cloud:confluence:[^:]+:content\/([^/]+)/);
|
|
15
|
-
if (match) {
|
|
16
|
-
return match[1];
|
|
17
|
-
}
|
|
18
|
-
throw new Error(`Invalid content property ARI: ${ari}`);
|
|
19
|
-
};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { getCloudId } from '../constants';
|
|
2
|
-
import { getConfluencePageAri } from './ari';
|
|
3
|
-
|
|
4
|
-
// Uncomment for proxy in Atlaskit, which will route to hello.atlassian.net
|
|
5
|
-
// const BASE_URL = 'https://localhost:9876';
|
|
6
|
-
const BASE_URL = `/gateway/api/ex/confluence/${getCloudId()}`;
|
|
7
|
-
const API_BASE_URL = `${BASE_URL}/wiki/api/v2`;
|
|
8
|
-
const COMMON_HEADERS = {
|
|
9
|
-
'Content-Type': 'application/json',
|
|
10
|
-
Accept: 'application/json'
|
|
11
|
-
};
|
|
12
|
-
const getContentPropertiesUrl = ({
|
|
13
|
-
pageId,
|
|
14
|
-
contentPropertyId
|
|
15
|
-
}) => {
|
|
16
|
-
const url = `${API_BASE_URL}/pages/${pageId}/properties`;
|
|
17
|
-
if (contentPropertyId) {
|
|
18
|
-
return `${url}/${contentPropertyId}`;
|
|
19
|
-
}
|
|
20
|
-
return url;
|
|
21
|
-
};
|
|
22
|
-
const getGraphQLPropertiesUrl = () => {
|
|
23
|
-
return `/cgraphql/api/graphql`;
|
|
24
|
-
};
|
|
25
|
-
export const createContentProperty = async ({
|
|
26
|
-
pageId,
|
|
27
|
-
key,
|
|
28
|
-
value
|
|
29
|
-
}) => {
|
|
30
|
-
const url = getContentPropertiesUrl({
|
|
31
|
-
pageId
|
|
32
|
-
});
|
|
33
|
-
const body = JSON.stringify({
|
|
34
|
-
key,
|
|
35
|
-
value
|
|
36
|
-
});
|
|
37
|
-
const response = await fetch(url, {
|
|
38
|
-
method: 'POST',
|
|
39
|
-
headers: COMMON_HEADERS,
|
|
40
|
-
body
|
|
41
|
-
});
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`Failed to create content property: ${response.statusText}`);
|
|
44
|
-
}
|
|
45
|
-
const contentProperty = await response.json();
|
|
46
|
-
return contentProperty;
|
|
47
|
-
};
|
|
48
|
-
export const getContentProperty = async ({
|
|
49
|
-
pageId,
|
|
50
|
-
contentPropertyId,
|
|
51
|
-
signal
|
|
52
|
-
}) => {
|
|
53
|
-
const url = getContentPropertiesUrl({
|
|
54
|
-
pageId,
|
|
55
|
-
contentPropertyId
|
|
56
|
-
});
|
|
57
|
-
const response = await fetch(url, {
|
|
58
|
-
method: 'GET',
|
|
59
|
-
headers: COMMON_HEADERS,
|
|
60
|
-
signal
|
|
61
|
-
});
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
throw new Error(`Failed to get content property: ${response.statusText}`);
|
|
64
|
-
}
|
|
65
|
-
const contentProperty = await response.json();
|
|
66
|
-
return contentProperty;
|
|
67
|
-
};
|
|
68
|
-
const getQuery = (documentARI, key, value) => {
|
|
69
|
-
return `mutation {
|
|
70
|
-
confluence {
|
|
71
|
-
updateValuePageProperty(input: {
|
|
72
|
-
pageId: "${documentARI}",
|
|
73
|
-
key: "${key}",
|
|
74
|
-
value: "${value}",
|
|
75
|
-
useSameVersion: true
|
|
76
|
-
}) {
|
|
77
|
-
pageProperty {
|
|
78
|
-
key,
|
|
79
|
-
value
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}`;
|
|
84
|
-
};
|
|
85
|
-
export const updateContentProperty = async ({
|
|
86
|
-
pageId,
|
|
87
|
-
key,
|
|
88
|
-
value
|
|
89
|
-
}) => {
|
|
90
|
-
const url = getGraphQLPropertiesUrl();
|
|
91
|
-
const documentARI = getConfluencePageAri(pageId);
|
|
92
|
-
|
|
93
|
-
// eslint-disable-next-line require-unicode-regexp
|
|
94
|
-
const query = getQuery(documentARI, key, value.replace(/"/g, '\\"'));
|
|
95
|
-
const bodyData = {
|
|
96
|
-
query
|
|
97
|
-
};
|
|
98
|
-
const response = await fetch(url, {
|
|
99
|
-
method: 'POST',
|
|
100
|
-
headers: COMMON_HEADERS,
|
|
101
|
-
body: JSON.stringify(bodyData)
|
|
102
|
-
});
|
|
103
|
-
if (!response.ok) {
|
|
104
|
-
throw new Error(`Failed to update content property: ${response.statusText}`);
|
|
105
|
-
}
|
|
106
|
-
const contentProperty = await response.json();
|
|
107
|
-
return contentProperty;
|
|
108
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export const SYNCED_BLOCK_EXTENSION_TYPE = 'com.atlassian.platform.extensions';
|
|
2
|
-
export const SYNCED_BLOCK_EXTENSION_KEY = 'synced-block';
|
|
3
|
-
export const SYNCED_BLOCK_SOURCE_NODE = 'source';
|
|
4
|
-
export const SYNCED_BLOCK_SOURCE_KEY = `${SYNCED_BLOCK_EXTENSION_KEY}:${SYNCED_BLOCK_SOURCE_NODE}`;
|
|
5
|
-
export const SYNCED_BLOCK_REFERENCE_NODE = 'reference';
|
|
6
|
-
export const SYNCED_BLOCK_REFERENCE_KEY = `${SYNCED_BLOCK_EXTENSION_KEY}:${SYNCED_BLOCK_REFERENCE_NODE}`;
|
|
7
|
-
export const isSyncedBlockAttributes = attributes => {
|
|
8
|
-
return !!attributes && typeof attributes === 'object' && 'extensionKey' in attributes && (attributes.extensionKey === SYNCED_BLOCK_SOURCE_KEY || attributes.extensionKey === SYNCED_BLOCK_REFERENCE_KEY);
|
|
9
|
-
};
|
|
10
|
-
export const getDefaultSyncedBlockContent = () => {
|
|
11
|
-
const attributes = {
|
|
12
|
-
extensionType: SYNCED_BLOCK_EXTENSION_TYPE,
|
|
13
|
-
extensionKey: SYNCED_BLOCK_SOURCE_KEY,
|
|
14
|
-
parameters: {
|
|
15
|
-
sourceDocumentAri: '',
|
|
16
|
-
contentAri: '',
|
|
17
|
-
contentPropertyKey: ''
|
|
18
|
-
},
|
|
19
|
-
localId: ''
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
type: 'bodiedExtension',
|
|
23
|
-
attrs: attributes,
|
|
24
|
-
content: [{
|
|
25
|
-
type: 'paragraph',
|
|
26
|
-
content: [{
|
|
27
|
-
type: 'text',
|
|
28
|
-
text: 'This is a synced block. Please edit the source document to update the content.'
|
|
29
|
-
}]
|
|
30
|
-
}]
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
export const parseSyncedBlockContentPropertyValue = value => {
|
|
34
|
-
try {
|
|
35
|
-
if (typeof value === 'string') {
|
|
36
|
-
return JSON.parse(value);
|
|
37
|
-
}
|
|
38
|
-
return value;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
// eslint-disable-next-line no-console
|
|
41
|
-
console.error('Failed to parse synced block content:', error);
|
|
42
|
-
return {
|
|
43
|
-
adf: getDefaultSyncedBlockContent()
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
export const stringifySyncedBlockContentPropertyValue = value => {
|
|
48
|
-
try {
|
|
49
|
-
return JSON.stringify(value);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
// eslint-disable-next-line no-console
|
|
52
|
-
console.error('Failed to serialize synced block content:', error);
|
|
53
|
-
return JSON.stringify({
|
|
54
|
-
adf: getDefaultSyncedBlockContent()
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
};
|