@atlaskit/editor-plugin-synced-block 5.2.2 → 5.3.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 5.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`870c3baec758b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/870c3baec758b) -
8
+ Enable consumers to use GraphQL subscription for fetching the block data when the block changes
9
+
10
+ ### Patch Changes
11
+
12
+ - [`5c522f81f181e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5c522f81f181e) -
13
+ [ux] EDITOR-4369 Support synced location for references on Jira in source and reference synced
14
+ block
15
+ - [`058065aadf69f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/058065aadf69f) -
16
+ [ux] [EDITOR-2851] Support reference sync block unsyc
17
+ - Updated dependencies
18
+
3
19
  ## 5.2.2
4
20
 
5
21
  ### Patch Changes
@@ -3,15 +3,18 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.removeSyncedBlock = exports.editSyncedBlockSource = exports.createSyncedBlock = exports.copySyncedBlockReferenceToClipboardEditorCommand = exports.copySyncedBlockReferenceToClipboard = void 0;
6
+ exports.unsync = exports.removeSyncedBlock = exports.editSyncedBlockSource = exports.createSyncedBlock = exports.copySyncedBlockReferenceToClipboardEditorCommand = exports.copySyncedBlockReferenceToClipboard = void 0;
7
+ var _schemaDefault = require("@atlaskit/adf-schema/schema-default");
7
8
  var _analytics = require("@atlaskit/editor-common/analytics");
8
9
  var _copyButton = require("@atlaskit/editor-common/copy-button");
10
+ var _model = require("@atlaskit/editor-prosemirror/model");
9
11
  var _state = require("@atlaskit/editor-prosemirror/state");
10
12
  var _utils = require("@atlaskit/editor-prosemirror/utils");
11
13
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
12
14
  var _main = require("../pm-plugins/main");
13
15
  var _utils2 = require("../pm-plugins/utils/utils");
14
16
  var _types = require("../types");
17
+ var _utils3 = require("./utils");
15
18
  var createSyncedBlock = exports.createSyncedBlock = function createSyncedBlock(_ref) {
16
19
  var tr = _ref.tr,
17
20
  syncBlockStore = _ref.syncBlockStore,
@@ -174,4 +177,33 @@ var removeSyncedBlock = exports.removeSyncedBlock = function removeSyncedBlock(a
174
177
  api === null || api === void 0 || api.core.actions.focus();
175
178
  return true;
176
179
  };
180
+ };
181
+
182
+ /**
183
+ * Deletes (bodied)SyncBlock node and paste its content to the editor
184
+ */
185
+ var unsync = exports.unsync = function unsync(storeManager, isBodiedSyncBlock, view) {
186
+ var _storeManager$referen;
187
+ if (!view) {
188
+ return false;
189
+ }
190
+ var state = view.state;
191
+ var syncBlock = (0, _utils2.findSyncBlockOrBodiedSyncBlock)(state.schema, state.selection);
192
+ if (!syncBlock) {
193
+ return false;
194
+ }
195
+ if (isBodiedSyncBlock) {
196
+ return true;
197
+ }
198
+
199
+ // handle syncBlock unsync
200
+ var syncBlockContent = (_storeManager$referen = storeManager.referenceManager.getFromCache(syncBlock.node.attrs.resourceId)) === null || _storeManager$referen === void 0 || (_storeManager$referen = _storeManager$referen.data) === null || _storeManager$referen === void 0 ? void 0 : _storeManager$referen.content;
201
+ if (!syncBlockContent) {
202
+ return false;
203
+ }
204
+
205
+ // use defaultSchema for serialization so we can serialize any type of nodes and marks despite current editor's schema might not allow it
206
+ var contentFragment = _model.Fragment.fromJSON(_schemaDefault.defaultSchema, syncBlockContent);
207
+ var contentDOM = _model.DOMSerializer.fromSchema(_schemaDefault.defaultSchema).serializeFragment(contentFragment);
208
+ return (0, _utils3.pasteSyncBlockHTMLContent)(contentDOM, view);
177
209
  };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.pasteSyncBlockHTMLContent = void 0;
7
+ var pasteSyncBlockHTMLContent = exports.pasteSyncBlockHTMLContent = function pasteSyncBlockHTMLContent(contentDOM, view) {
8
+ var tmpDiv = document.createElement('div');
9
+ tmpDiv.appendChild(contentDOM);
10
+
11
+ // This is required so that prosemirror can read the fragment context and slice properly
12
+ if (tmpDiv.firstChild instanceof HTMLElement) {
13
+ tmpDiv.firstChild.setAttribute('data-pm-slice', '0 0 []');
14
+
15
+ // As per requirement - when unsync reference block, it should render its content as copy&paste behaviour
16
+ // Hence here we call pasteHTML to evoke editor paste logic that handles any unsupported nodes/marks
17
+ return view.pasteHTML(tmpDiv.innerHTML);
18
+ }
19
+ return false;
20
+ };
@@ -6,10 +6,14 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.SyncBlockRefresher = exports.SYNC_BLOCK_FETCH_INTERVAL = void 0;
7
7
  var _react = require("react");
8
8
  var _hooks = require("@atlaskit/editor-common/hooks");
9
+ var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
10
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
9
11
  var SYNC_BLOCK_FETCH_INTERVAL = exports.SYNC_BLOCK_FETCH_INTERVAL = 3000;
10
12
 
11
- // Component that refreshes synced block subscriptions at regular intervals
12
- // this is a workaround for the subscription mechanism not being real-time
13
+ // Component that manages synced block data synchronization.
14
+ // When the feature flag 'platform_synced_block_dogfooding' is enabled,
15
+ // it uses provider-based GraphQL subscriptions for updates.
16
+ // When disabled, it falls back to polling at regular intervals.
13
17
  var SyncBlockRefresher = exports.SyncBlockRefresher = function SyncBlockRefresher(_ref) {
14
18
  var syncBlockStoreManager = _ref.syncBlockStoreManager,
15
19
  api = _ref.api;
@@ -20,9 +24,19 @@ var SyncBlockRefresher = exports.SyncBlockRefresher = function SyncBlockRefreshe
20
24
  };
21
25
  }),
22
26
  mode = _useSharedPluginState.mode;
27
+ var featureFlagEnabled = (0, _platformFeatureFlags.fg)('platform_synced_block_dogfooding');
28
+ var isOnline = !(0, _editorPluginConnectivity.isOfflineMode)(mode);
23
29
  (0, _react.useEffect)(function () {
30
+ var useRealTimeSubscriptions = featureFlagEnabled && isOnline;
31
+ syncBlockStoreManager.referenceManager.setRealTimeSubscriptionsEnabled(useRealTimeSubscriptions);
32
+ }, [syncBlockStoreManager, featureFlagEnabled, isOnline]);
33
+ (0, _react.useEffect)(function () {
34
+ var useRealTimeSubscriptions = featureFlagEnabled && isOnline;
35
+ if (useRealTimeSubscriptions) {
36
+ return;
37
+ }
24
38
  var interval = -1;
25
- if (mode !== 'offline') {
39
+ if (isOnline) {
26
40
  interval = window.setInterval(function () {
27
41
  var _document;
28
42
  // check if document is visible to avoid unnecessary refreshes
@@ -36,6 +50,6 @@ var SyncBlockRefresher = exports.SyncBlockRefresher = function SyncBlockRefreshe
36
50
  return function () {
37
51
  window.clearInterval(interval);
38
52
  };
39
- }, [syncBlockStoreManager, mode]);
53
+ }, [syncBlockStoreManager, isOnline, featureFlagEnabled]);
40
54
  return null;
41
55
  };
@@ -56,16 +56,17 @@ var styles = {
56
56
  var ItemTitle = function ItemTitle(_ref) {
57
57
  var title = _ref.title,
58
58
  formatMessage = _ref.formatMessage,
59
- onSamePage = _ref.onSamePage,
59
+ onSameDocument = _ref.onSameDocument,
60
60
  isSource = _ref.isSource,
61
- hasAccess = _ref.hasAccess;
61
+ hasAccess = _ref.hasAccess,
62
+ productType = _ref.productType;
62
63
  return /*#__PURE__*/React.createElement(_compiled.Inline, null, /*#__PURE__*/React.createElement(_compiled.Box, {
63
64
  as: "span",
64
65
  xcss: styles.title
65
- }, title), onSamePage && /*#__PURE__*/React.createElement(_compiled.Box, {
66
+ }, title), onSameDocument && /*#__PURE__*/React.createElement(_compiled.Box, {
66
67
  as: "span",
67
68
  xcss: styles.note
68
- }, "\xA0- ", formatMessage(_messages.syncBlockMessages.syncedLocationDropdownTitleNote)), isSource && /*#__PURE__*/React.createElement(_compiled.Box, {
69
+ }, "\xA0- ", formatMessage(productType === 'confluence-page' ? _messages.syncBlockMessages.syncedLocationDropdownTitleNoteForConfluencePage : _messages.syncBlockMessages.syncedLocationDropdownTitleNoteForJiraWorkItem)), isSource && /*#__PURE__*/React.createElement(_compiled.Box, {
69
70
  as: "span",
70
71
  xcss: styles.lozenge
71
72
  }, /*#__PURE__*/React.createElement(_lozenge.default, null, formatMessage(_messages.syncBlockMessages.syncedLocationDropdownSourceLozenge))), !hasAccess && /*#__PURE__*/React.createElement(_compiled.Box, {
@@ -82,7 +83,7 @@ var subTypeIconMap = {
82
83
  page: _page.default,
83
84
  blogpost: _quotationMark.default
84
85
  };
85
- var getSubTypeIcon = function getSubTypeIcon(subType) {
86
+ var getConfluenceSubTypeIcon = function getConfluenceSubTypeIcon(subType) {
86
87
  return subType && subType in subTypeIconMap ? subTypeIconMap[subType] : _page.default;
87
88
  };
88
89
  var ProductIcon = function ProductIcon(_ref2) {
@@ -99,17 +100,18 @@ var ProductIcon = function ProductIcon(_ref2) {
99
100
  var ItemIcon = function ItemIcon(_ref3) {
100
101
  var reference = _ref3.reference;
101
102
  var hasAccess = reference.hasAccess,
102
- subType = reference.subType;
103
- if (hasAccess) {
103
+ subType = reference.subType,
104
+ productType = reference.productType;
105
+ if (productType === 'confluence-page' && hasAccess) {
104
106
  return /*#__PURE__*/React.createElement(_icon.IconTile, {
105
- icon: getSubTypeIcon(subType),
107
+ icon: getConfluenceSubTypeIcon(subType),
106
108
  label: "",
107
109
  appearance: 'gray',
108
110
  size: "xsmall"
109
111
  });
110
112
  }
111
113
  return /*#__PURE__*/React.createElement(ProductIcon, {
112
- product: reference.productType
114
+ product: productType
113
115
  });
114
116
  };
115
117
  var processReferenceData = exports.processReferenceData = function processReferenceData(referenceData, intl) {
@@ -277,9 +279,10 @@ var DropdownContent = function DropdownContent(_ref7) {
277
279
  }, /*#__PURE__*/React.createElement(ItemTitle, {
278
280
  title: reference.title || reference.url || '',
279
281
  formatMessage: formatMessage,
280
- onSamePage: reference.onSamePage,
282
+ onSameDocument: reference.onSameDocument,
281
283
  isSource: reference.isSource,
282
- hasAccess: reference.hasAccess
284
+ hasAccess: reference.hasAccess,
285
+ productType: reference.productType
283
286
  }))));
284
287
  })));
285
288
  } else {
@@ -9,12 +9,14 @@ exports.getToolbarConfig = void 0;
9
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
10
  var _react = _interopRequireDefault(require("react"));
11
11
  var _messages = _interopRequireWildcard(require("@atlaskit/editor-common/messages"));
12
+ var _ui = require("@atlaskit/editor-common/ui");
12
13
  var _utils = require("@atlaskit/editor-prosemirror/utils");
13
14
  var _consts = require("@atlaskit/editor-shared-styles/consts");
14
15
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
15
16
  var _copy = _interopRequireDefault(require("@atlaskit/icon/core/copy"));
16
17
  var _delete = _interopRequireDefault(require("@atlaskit/icon/core/delete"));
17
18
  var _edit = _interopRequireDefault(require("@atlaskit/icon/core/edit"));
19
+ var _linkBroken = _interopRequireDefault(require("@atlaskit/icon/core/link-broken"));
18
20
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
19
21
  var _editorCommands = require("../editor-commands");
20
22
  var _utils2 = require("../pm-plugins/utils/utils");
@@ -73,7 +75,23 @@ var getToolbarConfig = exports.getToolbarConfig = function getToolbarConfig(stat
73
75
  });
74
76
  }
75
77
  };
76
- items.push(syncedLocation);
78
+ var unsyncButton = {
79
+ type: 'custom',
80
+ fallback: [],
81
+ render: function render(view) {
82
+ return /*#__PURE__*/_react.default.createElement(_ui.FloatingToolbarButton, {
83
+ areAnyNewToolbarFlagsEnabled: true,
84
+ icon: /*#__PURE__*/_react.default.createElement(_linkBroken.default, {
85
+ label: ""
86
+ }),
87
+ title: formatMessage(_messages.syncBlockMessages.unsyncButton),
88
+ onClick: function onClick() {
89
+ return (0, _editorCommands.unsync)(syncBlockStore, isBodiedSyncBlock, view);
90
+ }
91
+ });
92
+ }
93
+ };
94
+ items.push(syncedLocation, unsyncButton);
77
95
  }
78
96
  var copyButton = _objectSpread({
79
97
  id: 'editor.syncedBlock.copy',
@@ -1,11 +1,14 @@
1
+ import { defaultSchema } from '@atlaskit/adf-schema/schema-default';
1
2
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
3
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
4
+ import { DOMSerializer, Fragment } from '@atlaskit/editor-prosemirror/model';
3
5
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
4
6
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
5
7
  import { fg } from '@atlaskit/platform-feature-flags';
6
8
  import { syncedBlockPluginKey } from '../pm-plugins/main';
7
9
  import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
8
10
  import { FLAG_ID } from '../types';
11
+ import { pasteSyncBlockHTMLContent } from './utils';
9
12
  export const createSyncedBlock = ({
10
13
  tr,
11
14
  syncBlockStore,
@@ -176,4 +179,35 @@ export const removeSyncedBlock = api => (state, dispatch, _view) => {
176
179
  dispatch(removeTr);
177
180
  api === null || api === void 0 ? void 0 : api.core.actions.focus();
178
181
  return true;
182
+ };
183
+
184
+ /**
185
+ * Deletes (bodied)SyncBlock node and paste its content to the editor
186
+ */
187
+ export const unsync = (storeManager, isBodiedSyncBlock, view) => {
188
+ var _storeManager$referen, _storeManager$referen2;
189
+ if (!view) {
190
+ return false;
191
+ }
192
+ const {
193
+ state
194
+ } = view;
195
+ const syncBlock = findSyncBlockOrBodiedSyncBlock(state.schema, state.selection);
196
+ if (!syncBlock) {
197
+ return false;
198
+ }
199
+ if (isBodiedSyncBlock) {
200
+ return true;
201
+ }
202
+
203
+ // handle syncBlock unsync
204
+ const syncBlockContent = (_storeManager$referen = storeManager.referenceManager.getFromCache(syncBlock.node.attrs.resourceId)) === null || _storeManager$referen === void 0 ? void 0 : (_storeManager$referen2 = _storeManager$referen.data) === null || _storeManager$referen2 === void 0 ? void 0 : _storeManager$referen2.content;
205
+ if (!syncBlockContent) {
206
+ return false;
207
+ }
208
+
209
+ // use defaultSchema for serialization so we can serialize any type of nodes and marks despite current editor's schema might not allow it
210
+ const contentFragment = Fragment.fromJSON(defaultSchema, syncBlockContent);
211
+ const contentDOM = DOMSerializer.fromSchema(defaultSchema).serializeFragment(contentFragment);
212
+ return pasteSyncBlockHTMLContent(contentDOM, view);
179
213
  };
@@ -0,0 +1,14 @@
1
+ export const pasteSyncBlockHTMLContent = (contentDOM, view) => {
2
+ const tmpDiv = document.createElement('div');
3
+ tmpDiv.appendChild(contentDOM);
4
+
5
+ // This is required so that prosemirror can read the fragment context and slice properly
6
+ if (tmpDiv.firstChild instanceof HTMLElement) {
7
+ tmpDiv.firstChild.setAttribute('data-pm-slice', '0 0 []');
8
+
9
+ // As per requirement - when unsync reference block, it should render its content as copy&paste behaviour
10
+ // Hence here we call pasteHTML to evoke editor paste logic that handles any unsupported nodes/marks
11
+ return view.pasteHTML(tmpDiv.innerHTML);
12
+ }
13
+ return false;
14
+ };
@@ -1,9 +1,13 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
+ import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
3
5
  export const SYNC_BLOCK_FETCH_INTERVAL = 3000;
4
6
 
5
- // Component that refreshes synced block subscriptions at regular intervals
6
- // this is a workaround for the subscription mechanism not being real-time
7
+ // Component that manages synced block data synchronization.
8
+ // When the feature flag 'platform_synced_block_dogfooding' is enabled,
9
+ // it uses provider-based GraphQL subscriptions for updates.
10
+ // When disabled, it falls back to polling at regular intervals.
7
11
  export const SyncBlockRefresher = ({
8
12
  syncBlockStoreManager,
9
13
  api
@@ -16,9 +20,19 @@ export const SyncBlockRefresher = ({
16
20
  mode: (_states$connectivityS = states.connectivityState) === null || _states$connectivityS === void 0 ? void 0 : _states$connectivityS.mode
17
21
  };
18
22
  });
23
+ const featureFlagEnabled = fg('platform_synced_block_dogfooding');
24
+ const isOnline = !isOfflineMode(mode);
19
25
  useEffect(() => {
26
+ const useRealTimeSubscriptions = featureFlagEnabled && isOnline;
27
+ syncBlockStoreManager.referenceManager.setRealTimeSubscriptionsEnabled(useRealTimeSubscriptions);
28
+ }, [syncBlockStoreManager, featureFlagEnabled, isOnline]);
29
+ useEffect(() => {
30
+ const useRealTimeSubscriptions = featureFlagEnabled && isOnline;
31
+ if (useRealTimeSubscriptions) {
32
+ return;
33
+ }
20
34
  let interval = -1;
21
- if (mode !== 'offline') {
35
+ if (isOnline) {
22
36
  interval = window.setInterval(() => {
23
37
  var _document;
24
38
  // check if document is visible to avoid unnecessary refreshes
@@ -32,6 +46,6 @@ export const SyncBlockRefresher = ({
32
46
  return () => {
33
47
  window.clearInterval(interval);
34
48
  };
35
- }, [syncBlockStoreManager, mode]);
49
+ }, [syncBlockStoreManager, isOnline, featureFlagEnabled]);
36
50
  return null;
37
51
  };
@@ -39,17 +39,18 @@ const styles = {
39
39
  const ItemTitle = ({
40
40
  title,
41
41
  formatMessage,
42
- onSamePage,
42
+ onSameDocument,
43
43
  isSource,
44
- hasAccess
44
+ hasAccess,
45
+ productType
45
46
  }) => {
46
47
  return /*#__PURE__*/React.createElement(Inline, null, /*#__PURE__*/React.createElement(Box, {
47
48
  as: "span",
48
49
  xcss: styles.title
49
- }, title), onSamePage && /*#__PURE__*/React.createElement(Box, {
50
+ }, title), onSameDocument && /*#__PURE__*/React.createElement(Box, {
50
51
  as: "span",
51
52
  xcss: styles.note
52
- }, "\xA0- ", formatMessage(messages.syncedLocationDropdownTitleNote)), isSource && /*#__PURE__*/React.createElement(Box, {
53
+ }, "\xA0- ", formatMessage(productType === 'confluence-page' ? messages.syncedLocationDropdownTitleNoteForConfluencePage : messages.syncedLocationDropdownTitleNoteForJiraWorkItem)), isSource && /*#__PURE__*/React.createElement(Box, {
53
54
  as: "span",
54
55
  xcss: styles.lozenge
55
56
  }, /*#__PURE__*/React.createElement(Lozenge, null, formatMessage(messages.syncedLocationDropdownSourceLozenge))), !hasAccess && /*#__PURE__*/React.createElement(Box, {
@@ -66,7 +67,7 @@ const subTypeIconMap = {
66
67
  page: PageIcon,
67
68
  blogpost: QuotationMarkIcon
68
69
  };
69
- const getSubTypeIcon = subType => {
70
+ const getConfluenceSubTypeIcon = subType => {
70
71
  return subType && subType in subTypeIconMap ? subTypeIconMap[subType] : PageIcon;
71
72
  };
72
73
  const ProductIcon = ({
@@ -86,18 +87,19 @@ const ItemIcon = ({
86
87
  }) => {
87
88
  const {
88
89
  hasAccess,
89
- subType
90
+ subType,
91
+ productType
90
92
  } = reference;
91
- if (hasAccess) {
93
+ if (productType === 'confluence-page' && hasAccess) {
92
94
  return /*#__PURE__*/React.createElement(IconTile, {
93
- icon: getSubTypeIcon(subType),
95
+ icon: getConfluenceSubTypeIcon(subType),
94
96
  label: "",
95
97
  appearance: 'gray',
96
98
  size: "xsmall"
97
99
  });
98
100
  }
99
101
  return /*#__PURE__*/React.createElement(ProductIcon, {
100
- product: reference.productType
102
+ product: productType
101
103
  });
102
104
  };
103
105
  export const processReferenceData = (referenceData, intl) => {
@@ -230,9 +232,10 @@ const DropdownContent = ({
230
232
  }, /*#__PURE__*/React.createElement(ItemTitle, {
231
233
  title: reference.title || reference.url || '',
232
234
  formatMessage: formatMessage,
233
- onSamePage: reference.onSamePage,
235
+ onSameDocument: reference.onSameDocument,
234
236
  isSource: reference.isSource,
235
- hasAccess: reference.hasAccess
237
+ hasAccess: reference.hasAccess,
238
+ productType: reference.productType
236
239
  })))))));
237
240
  } else {
238
241
  return /*#__PURE__*/React.createElement(NoResultScreen, {
@@ -1,13 +1,15 @@
1
1
  import React from 'react';
2
2
  import commonMessages, { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
3
+ import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui';
3
4
  import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
4
5
  import { akEditorSelectedNodeClassName } from '@atlaskit/editor-shared-styles/consts';
5
6
  import { SyncBlockError } from '@atlaskit/editor-synced-block-provider';
6
7
  import CopyIcon from '@atlaskit/icon/core/copy';
7
8
  import DeleteIcon from '@atlaskit/icon/core/delete';
8
9
  import EditIcon from '@atlaskit/icon/core/edit';
10
+ import LinkBrokenIcon from '@atlaskit/icon/core/link-broken';
9
11
  import { fg } from '@atlaskit/platform-feature-flags';
10
- import { copySyncedBlockReferenceToClipboard, editSyncedBlockSource, removeSyncedBlock } from '../editor-commands';
12
+ import { copySyncedBlockReferenceToClipboard, editSyncedBlockSource, removeSyncedBlock, unsync } from '../editor-commands';
11
13
  import { findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
12
14
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
13
15
  import { SyncedLocationDropdown } from './SyncedLocationDropdown';
@@ -69,7 +71,21 @@ export const getToolbarConfig = (state, intl, api, syncBlockStore) => {
69
71
  });
70
72
  }
71
73
  };
72
- items.push(syncedLocation);
74
+ const unsyncButton = {
75
+ type: 'custom',
76
+ fallback: [],
77
+ render: view => {
78
+ return /*#__PURE__*/React.createElement(Button, {
79
+ areAnyNewToolbarFlagsEnabled: true,
80
+ icon: /*#__PURE__*/React.createElement(LinkBrokenIcon, {
81
+ label: ""
82
+ }),
83
+ title: formatMessage(messages.unsyncButton),
84
+ onClick: () => unsync(syncBlockStore, isBodiedSyncBlock, view)
85
+ });
86
+ }
87
+ };
88
+ items.push(syncedLocation, unsyncButton);
73
89
  }
74
90
  const copyButton = {
75
91
  id: 'editor.syncedBlock.copy',
@@ -1,11 +1,14 @@
1
+ import { defaultSchema } from '@atlaskit/adf-schema/schema-default';
1
2
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
3
  import { copyDomNode, toDOM } from '@atlaskit/editor-common/copy-button';
4
+ import { DOMSerializer, Fragment } from '@atlaskit/editor-prosemirror/model';
3
5
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
4
6
  import { findSelectedNodeOfType, removeParentNodeOfType, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils';
5
7
  import { fg } from '@atlaskit/platform-feature-flags';
6
8
  import { syncedBlockPluginKey } from '../pm-plugins/main';
7
9
  import { canBeConvertedToSyncBlock, findSyncBlock, findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
8
10
  import { FLAG_ID } from '../types';
11
+ import { pasteSyncBlockHTMLContent } from './utils';
9
12
  export var createSyncedBlock = function createSyncedBlock(_ref) {
10
13
  var tr = _ref.tr,
11
14
  syncBlockStore = _ref.syncBlockStore,
@@ -168,4 +171,33 @@ export var removeSyncedBlock = function removeSyncedBlock(api) {
168
171
  api === null || api === void 0 || api.core.actions.focus();
169
172
  return true;
170
173
  };
174
+ };
175
+
176
+ /**
177
+ * Deletes (bodied)SyncBlock node and paste its content to the editor
178
+ */
179
+ export var unsync = function unsync(storeManager, isBodiedSyncBlock, view) {
180
+ var _storeManager$referen;
181
+ if (!view) {
182
+ return false;
183
+ }
184
+ var state = view.state;
185
+ var syncBlock = findSyncBlockOrBodiedSyncBlock(state.schema, state.selection);
186
+ if (!syncBlock) {
187
+ return false;
188
+ }
189
+ if (isBodiedSyncBlock) {
190
+ return true;
191
+ }
192
+
193
+ // handle syncBlock unsync
194
+ var syncBlockContent = (_storeManager$referen = storeManager.referenceManager.getFromCache(syncBlock.node.attrs.resourceId)) === null || _storeManager$referen === void 0 || (_storeManager$referen = _storeManager$referen.data) === null || _storeManager$referen === void 0 ? void 0 : _storeManager$referen.content;
195
+ if (!syncBlockContent) {
196
+ return false;
197
+ }
198
+
199
+ // use defaultSchema for serialization so we can serialize any type of nodes and marks despite current editor's schema might not allow it
200
+ var contentFragment = Fragment.fromJSON(defaultSchema, syncBlockContent);
201
+ var contentDOM = DOMSerializer.fromSchema(defaultSchema).serializeFragment(contentFragment);
202
+ return pasteSyncBlockHTMLContent(contentDOM, view);
171
203
  };
@@ -0,0 +1,14 @@
1
+ export var pasteSyncBlockHTMLContent = function pasteSyncBlockHTMLContent(contentDOM, view) {
2
+ var tmpDiv = document.createElement('div');
3
+ tmpDiv.appendChild(contentDOM);
4
+
5
+ // This is required so that prosemirror can read the fragment context and slice properly
6
+ if (tmpDiv.firstChild instanceof HTMLElement) {
7
+ tmpDiv.firstChild.setAttribute('data-pm-slice', '0 0 []');
8
+
9
+ // As per requirement - when unsync reference block, it should render its content as copy&paste behaviour
10
+ // Hence here we call pasteHTML to evoke editor paste logic that handles any unsupported nodes/marks
11
+ return view.pasteHTML(tmpDiv.innerHTML);
12
+ }
13
+ return false;
14
+ };
@@ -1,9 +1,13 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
3
+ import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
3
5
  export var SYNC_BLOCK_FETCH_INTERVAL = 3000;
4
6
 
5
- // Component that refreshes synced block subscriptions at regular intervals
6
- // this is a workaround for the subscription mechanism not being real-time
7
+ // Component that manages synced block data synchronization.
8
+ // When the feature flag 'platform_synced_block_dogfooding' is enabled,
9
+ // it uses provider-based GraphQL subscriptions for updates.
10
+ // When disabled, it falls back to polling at regular intervals.
7
11
  export var SyncBlockRefresher = function SyncBlockRefresher(_ref) {
8
12
  var syncBlockStoreManager = _ref.syncBlockStoreManager,
9
13
  api = _ref.api;
@@ -14,9 +18,19 @@ export var SyncBlockRefresher = function SyncBlockRefresher(_ref) {
14
18
  };
15
19
  }),
16
20
  mode = _useSharedPluginState.mode;
21
+ var featureFlagEnabled = fg('platform_synced_block_dogfooding');
22
+ var isOnline = !isOfflineMode(mode);
17
23
  useEffect(function () {
24
+ var useRealTimeSubscriptions = featureFlagEnabled && isOnline;
25
+ syncBlockStoreManager.referenceManager.setRealTimeSubscriptionsEnabled(useRealTimeSubscriptions);
26
+ }, [syncBlockStoreManager, featureFlagEnabled, isOnline]);
27
+ useEffect(function () {
28
+ var useRealTimeSubscriptions = featureFlagEnabled && isOnline;
29
+ if (useRealTimeSubscriptions) {
30
+ return;
31
+ }
18
32
  var interval = -1;
19
- if (mode !== 'offline') {
33
+ if (isOnline) {
20
34
  interval = window.setInterval(function () {
21
35
  var _document;
22
36
  // check if document is visible to avoid unnecessary refreshes
@@ -30,6 +44,6 @@ export var SyncBlockRefresher = function SyncBlockRefresher(_ref) {
30
44
  return function () {
31
45
  window.clearInterval(interval);
32
46
  };
33
- }, [syncBlockStoreManager, mode]);
47
+ }, [syncBlockStoreManager, isOnline, featureFlagEnabled]);
34
48
  return null;
35
49
  };
@@ -47,16 +47,17 @@ var styles = {
47
47
  var ItemTitle = function ItemTitle(_ref) {
48
48
  var title = _ref.title,
49
49
  formatMessage = _ref.formatMessage,
50
- onSamePage = _ref.onSamePage,
50
+ onSameDocument = _ref.onSameDocument,
51
51
  isSource = _ref.isSource,
52
- hasAccess = _ref.hasAccess;
52
+ hasAccess = _ref.hasAccess,
53
+ productType = _ref.productType;
53
54
  return /*#__PURE__*/React.createElement(Inline, null, /*#__PURE__*/React.createElement(Box, {
54
55
  as: "span",
55
56
  xcss: styles.title
56
- }, title), onSamePage && /*#__PURE__*/React.createElement(Box, {
57
+ }, title), onSameDocument && /*#__PURE__*/React.createElement(Box, {
57
58
  as: "span",
58
59
  xcss: styles.note
59
- }, "\xA0- ", formatMessage(messages.syncedLocationDropdownTitleNote)), isSource && /*#__PURE__*/React.createElement(Box, {
60
+ }, "\xA0- ", formatMessage(productType === 'confluence-page' ? messages.syncedLocationDropdownTitleNoteForConfluencePage : messages.syncedLocationDropdownTitleNoteForJiraWorkItem)), isSource && /*#__PURE__*/React.createElement(Box, {
60
61
  as: "span",
61
62
  xcss: styles.lozenge
62
63
  }, /*#__PURE__*/React.createElement(Lozenge, null, formatMessage(messages.syncedLocationDropdownSourceLozenge))), !hasAccess && /*#__PURE__*/React.createElement(Box, {
@@ -73,7 +74,7 @@ var subTypeIconMap = {
73
74
  page: PageIcon,
74
75
  blogpost: QuotationMarkIcon
75
76
  };
76
- var getSubTypeIcon = function getSubTypeIcon(subType) {
77
+ var getConfluenceSubTypeIcon = function getConfluenceSubTypeIcon(subType) {
77
78
  return subType && subType in subTypeIconMap ? subTypeIconMap[subType] : PageIcon;
78
79
  };
79
80
  var ProductIcon = function ProductIcon(_ref2) {
@@ -90,17 +91,18 @@ var ProductIcon = function ProductIcon(_ref2) {
90
91
  var ItemIcon = function ItemIcon(_ref3) {
91
92
  var reference = _ref3.reference;
92
93
  var hasAccess = reference.hasAccess,
93
- subType = reference.subType;
94
- if (hasAccess) {
94
+ subType = reference.subType,
95
+ productType = reference.productType;
96
+ if (productType === 'confluence-page' && hasAccess) {
95
97
  return /*#__PURE__*/React.createElement(IconTile, {
96
- icon: getSubTypeIcon(subType),
98
+ icon: getConfluenceSubTypeIcon(subType),
97
99
  label: "",
98
100
  appearance: 'gray',
99
101
  size: "xsmall"
100
102
  });
101
103
  }
102
104
  return /*#__PURE__*/React.createElement(ProductIcon, {
103
- product: reference.productType
105
+ product: productType
104
106
  });
105
107
  };
106
108
  export var processReferenceData = function processReferenceData(referenceData, intl) {
@@ -268,9 +270,10 @@ var DropdownContent = function DropdownContent(_ref7) {
268
270
  }, /*#__PURE__*/React.createElement(ItemTitle, {
269
271
  title: reference.title || reference.url || '',
270
272
  formatMessage: formatMessage,
271
- onSamePage: reference.onSamePage,
273
+ onSameDocument: reference.onSameDocument,
272
274
  isSource: reference.isSource,
273
- hasAccess: reference.hasAccess
275
+ hasAccess: reference.hasAccess,
276
+ productType: reference.productType
274
277
  }))));
275
278
  })));
276
279
  } else {
@@ -3,14 +3,16 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
3
3
  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) { _defineProperty(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; }
4
4
  import React from 'react';
5
5
  import commonMessages, { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
6
+ import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui';
6
7
  import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
7
8
  import { akEditorSelectedNodeClassName } from '@atlaskit/editor-shared-styles/consts';
8
9
  import { SyncBlockError } from '@atlaskit/editor-synced-block-provider';
9
10
  import CopyIcon from '@atlaskit/icon/core/copy';
10
11
  import DeleteIcon from '@atlaskit/icon/core/delete';
11
12
  import EditIcon from '@atlaskit/icon/core/edit';
13
+ import LinkBrokenIcon from '@atlaskit/icon/core/link-broken';
12
14
  import { fg } from '@atlaskit/platform-feature-flags';
13
- import { copySyncedBlockReferenceToClipboard, editSyncedBlockSource, removeSyncedBlock } from '../editor-commands';
15
+ import { copySyncedBlockReferenceToClipboard, editSyncedBlockSource, removeSyncedBlock, unsync } from '../editor-commands';
14
16
  import { findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plugins/utils/utils';
15
17
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
16
18
  import { SyncedLocationDropdown } from './SyncedLocationDropdown';
@@ -64,7 +66,23 @@ export var getToolbarConfig = function getToolbarConfig(state, intl, api, syncBl
64
66
  });
65
67
  }
66
68
  };
67
- items.push(syncedLocation);
69
+ var unsyncButton = {
70
+ type: 'custom',
71
+ fallback: [],
72
+ render: function render(view) {
73
+ return /*#__PURE__*/React.createElement(Button, {
74
+ areAnyNewToolbarFlagsEnabled: true,
75
+ icon: /*#__PURE__*/React.createElement(LinkBrokenIcon, {
76
+ label: ""
77
+ }),
78
+ title: formatMessage(messages.unsyncButton),
79
+ onClick: function onClick() {
80
+ return unsync(syncBlockStore, isBodiedSyncBlock, view);
81
+ }
82
+ });
83
+ }
84
+ };
85
+ items.push(syncedLocation, unsyncButton);
68
86
  }
69
87
  var copyButton = _objectSpread({
70
88
  id: 'editor.syncedBlock.copy',
@@ -1,6 +1,7 @@
1
1
  import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
2
  import type { Command, EditorCommand, ExtractInjectionAPI, TypeAheadInsert } from '@atlaskit/editor-common/types';
3
3
  import { type Transaction } from '@atlaskit/editor-prosemirror/state';
4
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
5
  import type { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
5
6
  import type { SyncedBlockPlugin } from '../syncedBlockPluginType';
6
7
  type createSyncedBlockProps = {
@@ -14,4 +15,8 @@ export declare const copySyncedBlockReferenceToClipboardEditorCommand: (syncBloc
14
15
  export declare const copySyncedBlockReferenceToClipboard: (syncBlockStore: SyncBlockStoreManager, api?: ExtractInjectionAPI<SyncedBlockPlugin>) => Command;
15
16
  export declare const editSyncedBlockSource: (syncBlockStore: SyncBlockStoreManager, api?: ExtractInjectionAPI<SyncedBlockPlugin>) => Command;
16
17
  export declare const removeSyncedBlock: (api?: ExtractInjectionAPI<SyncedBlockPlugin>) => Command;
18
+ /**
19
+ * Deletes (bodied)SyncBlock node and paste its content to the editor
20
+ */
21
+ export declare const unsync: (storeManager: SyncBlockStoreManager, isBodiedSyncBlock: boolean, view?: EditorView) => boolean;
17
22
  export {};
@@ -0,0 +1,2 @@
1
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
2
+ export declare const pasteSyncBlockHTMLContent: (contentDOM: HTMLElement | DocumentFragment, view: EditorView) => boolean;
@@ -1,6 +1,7 @@
1
1
  import { type DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
2
2
  import type { Command, EditorCommand, ExtractInjectionAPI, TypeAheadInsert } from '@atlaskit/editor-common/types';
3
3
  import { type Transaction } from '@atlaskit/editor-prosemirror/state';
4
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
4
5
  import type { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
5
6
  import type { SyncedBlockPlugin } from '../syncedBlockPluginType';
6
7
  type createSyncedBlockProps = {
@@ -14,4 +15,8 @@ export declare const copySyncedBlockReferenceToClipboardEditorCommand: (syncBloc
14
15
  export declare const copySyncedBlockReferenceToClipboard: (syncBlockStore: SyncBlockStoreManager, api?: ExtractInjectionAPI<SyncedBlockPlugin>) => Command;
15
16
  export declare const editSyncedBlockSource: (syncBlockStore: SyncBlockStoreManager, api?: ExtractInjectionAPI<SyncedBlockPlugin>) => Command;
16
17
  export declare const removeSyncedBlock: (api?: ExtractInjectionAPI<SyncedBlockPlugin>) => Command;
18
+ /**
19
+ * Deletes (bodied)SyncBlock node and paste its content to the editor
20
+ */
21
+ export declare const unsync: (storeManager: SyncBlockStoreManager, isBodiedSyncBlock: boolean, view?: EditorView) => boolean;
17
22
  export {};
@@ -0,0 +1,2 @@
1
+ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
2
+ export declare const pasteSyncBlockHTMLContent: (contentDOM: HTMLElement | DocumentFragment, view: EditorView) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "5.2.2",
3
+ "version": "5.3.0",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -40,7 +40,7 @@
40
40
  "@atlaskit/editor-plugin-selection": "^7.0.0",
41
41
  "@atlaskit/editor-prosemirror": "^7.2.0",
42
42
  "@atlaskit/editor-shared-styles": "^3.10.0",
43
- "@atlaskit/editor-synced-block-provider": "^3.12.0",
43
+ "@atlaskit/editor-synced-block-provider": "^3.13.0",
44
44
  "@atlaskit/editor-tables": "^2.9.0",
45
45
  "@atlaskit/editor-toolbar": "^0.19.0",
46
46
  "@atlaskit/flag": "^17.8.0",
@@ -52,7 +52,7 @@
52
52
  "@atlaskit/platform-feature-flags": "^1.1.0",
53
53
  "@atlaskit/primitives": "^17.1.0",
54
54
  "@atlaskit/spinner": "19.0.9",
55
- "@atlaskit/tmp-editor-statsig": "^16.28.0",
55
+ "@atlaskit/tmp-editor-statsig": "^16.30.0",
56
56
  "@atlaskit/tokens": "10.1.0",
57
57
  "@atlaskit/tooltip": "^20.14.0",
58
58
  "@atlaskit/visually-hidden": "^3.0.0",