@atlaskit/editor-plugin-synced-block 5.2.2 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cjs/editor-commands/index.js +33 -1
  3. package/dist/cjs/editor-commands/utils.js +20 -0
  4. package/dist/cjs/ui/SyncBlockLabel.js +103 -9
  5. package/dist/cjs/ui/SyncBlockRefresher.js +18 -4
  6. package/dist/cjs/ui/SyncBlockRendererWrapper.js +6 -2
  7. package/dist/cjs/ui/SyncedLocationDropdown.js +14 -11
  8. package/dist/cjs/ui/floating-toolbar.js +19 -1
  9. package/dist/es2019/editor-commands/index.js +34 -0
  10. package/dist/es2019/editor-commands/utils.js +14 -0
  11. package/dist/es2019/ui/SyncBlockLabel.js +96 -7
  12. package/dist/es2019/ui/SyncBlockRefresher.js +18 -4
  13. package/dist/es2019/ui/SyncBlockRendererWrapper.js +6 -2
  14. package/dist/es2019/ui/SyncedLocationDropdown.js +14 -11
  15. package/dist/es2019/ui/floating-toolbar.js +18 -2
  16. package/dist/esm/editor-commands/index.js +32 -0
  17. package/dist/esm/editor-commands/utils.js +14 -0
  18. package/dist/esm/ui/SyncBlockLabel.js +100 -8
  19. package/dist/esm/ui/SyncBlockRefresher.js +18 -4
  20. package/dist/esm/ui/SyncBlockRendererWrapper.js +6 -2
  21. package/dist/esm/ui/SyncedLocationDropdown.js +14 -11
  22. package/dist/esm/ui/floating-toolbar.js +20 -2
  23. package/dist/types/editor-commands/index.d.ts +5 -0
  24. package/dist/types/editor-commands/utils.d.ts +2 -0
  25. package/dist/types/syncedBlockPluginType.d.ts +1 -1
  26. package/dist/types/ui/SyncBlockLabel.d.ts +4 -1
  27. package/dist/types/ui/SyncBlockRendererWrapper.d.ts +1 -1
  28. package/dist/types-ts4.5/editor-commands/index.d.ts +5 -0
  29. package/dist/types-ts4.5/editor-commands/utils.d.ts +2 -0
  30. package/dist/types-ts4.5/syncedBlockPluginType.d.ts +1 -1
  31. package/dist/types-ts4.5/ui/SyncBlockLabel.d.ts +4 -1
  32. package/dist/types-ts4.5/ui/SyncBlockRendererWrapper.d.ts +1 -1
  33. package/package.json +7 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 5.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`87abc5dda86fe`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/87abc5dda86fe) -
8
+ [ux] Show last edited time in sync block tooltip
9
+ - Updated dependencies
10
+
11
+ ## 5.3.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [`870c3baec758b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/870c3baec758b) -
16
+ Enable consumers to use GraphQL subscription for fetching the block data when the block changes
17
+
18
+ ### Patch Changes
19
+
20
+ - [`5c522f81f181e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5c522f81f181e) -
21
+ [ux] EDITOR-4369 Support synced location for references on Jira in source and reference synced
22
+ block
23
+ - [`058065aadf69f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/058065aadf69f) -
24
+ [ux] [EDITOR-2851] Support reference sync block unsyc
25
+ - Updated dependencies
26
+
3
27
  ## 5.2.2
4
28
 
5
29
  ### 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
+ };
@@ -1,38 +1,132 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
6
7
  });
7
- exports.SyncBlockLabel = void 0;
8
- var _react = _interopRequireDefault(require("react"));
8
+ exports.formatElapsedTime = exports.SyncBlockLabel = void 0;
9
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
+ var _react = _interopRequireWildcard(require("react"));
11
+ var _isYesterday = _interopRequireDefault(require("date-fns/isYesterday"));
9
12
  var _reactIntlNext = require("react-intl-next");
10
13
  var _messages = require("@atlaskit/editor-common/messages");
11
14
  var _syncBlock = require("@atlaskit/editor-common/sync-block");
12
15
  var _blockSynced = _interopRequireDefault(require("@atlaskit/icon-lab/core/block-synced"));
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
17
  var _compiled = require("@atlaskit/primitives/compiled");
14
18
  var _tooltip = _interopRequireDefault(require("@atlaskit/tooltip"));
15
19
  var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
20
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
16
21
  var SyncBlockLabelDataId = 'sync-block-label';
22
+ var SECONDS_IN_MINUTE = 60;
23
+ var SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
24
+ var SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
25
+ var SECONDS_IN_WEEK = SECONDS_IN_DAY * 7;
26
+ var SECONDS_IN_MONTH = SECONDS_IN_DAY * 30;
27
+ var SECONDS_IN_YEAR = SECONDS_IN_DAY * 365;
28
+ var formatElapsedTime = exports.formatElapsedTime = function formatElapsedTime(isoDate, intl) {
29
+ var now = Date.now();
30
+ var date = new Date(isoDate).getTime();
31
+ var diffInSeconds = Math.floor((now - date) / 1000);
32
+ var dateObj = new Date(isoDate);
33
+
34
+ // Show "yesterday" when timestamp is from the previous calendar day
35
+ if ((0, _isYesterday.default)(dateObj) && diffInSeconds >= SECONDS_IN_DAY) {
36
+ return intl.formatRelativeTime(-1, 'day', {
37
+ numeric: 'auto',
38
+ style: 'long'
39
+ });
40
+ }
41
+ if (diffInSeconds < SECONDS_IN_MINUTE) {
42
+ return intl.formatRelativeTime(-Math.max(diffInSeconds, 1), 'second', {
43
+ style: 'long'
44
+ });
45
+ } else if (diffInSeconds < SECONDS_IN_HOUR) {
46
+ var minutes = Math.floor(diffInSeconds / SECONDS_IN_MINUTE);
47
+ return intl.formatRelativeTime(-minutes, 'minute', {
48
+ style: 'long'
49
+ });
50
+ } else if (diffInSeconds < SECONDS_IN_DAY) {
51
+ var hours = Math.floor(diffInSeconds / SECONDS_IN_HOUR);
52
+ return intl.formatRelativeTime(-hours, 'hour', {
53
+ style: 'long'
54
+ });
55
+ } else if (diffInSeconds < SECONDS_IN_WEEK) {
56
+ var days = Math.floor(diffInSeconds / SECONDS_IN_DAY);
57
+ return intl.formatRelativeTime(-days, 'day', {
58
+ style: 'long'
59
+ });
60
+ } else if (diffInSeconds < SECONDS_IN_MONTH) {
61
+ var weeks = Math.floor(diffInSeconds / SECONDS_IN_WEEK);
62
+ return intl.formatRelativeTime(-weeks, 'week', {
63
+ style: 'long'
64
+ });
65
+ } else if (diffInSeconds < SECONDS_IN_YEAR) {
66
+ var months = Math.floor(diffInSeconds / SECONDS_IN_MONTH);
67
+ return intl.formatRelativeTime(-months, 'month', {
68
+ style: 'long'
69
+ });
70
+ } else {
71
+ var years = Math.floor(diffInSeconds / SECONDS_IN_YEAR);
72
+ return intl.formatRelativeTime(-years, 'year', {
73
+ style: 'long'
74
+ });
75
+ }
76
+ };
17
77
  var SyncBlockLabelComponent = function SyncBlockLabelComponent(_ref) {
18
78
  var isSource = _ref.isSource,
19
79
  useFetchSyncBlockTitle = _ref.useFetchSyncBlockTitle,
20
- localId = _ref.localId;
21
- var _useIntl = (0, _reactIntlNext.useIntl)(),
22
- formatMessage = _useIntl.formatMessage;
80
+ localId = _ref.localId,
81
+ contentUpdatedAt = _ref.contentUpdatedAt;
82
+ var intl = (0, _reactIntlNext.useIntl)();
83
+ var formatMessage = intl.formatMessage;
23
84
  var title = useFetchSyncBlockTitle === null || useFetchSyncBlockTitle === void 0 ? void 0 : useFetchSyncBlockTitle();
24
- var tooltipContent = isSource ? formatMessage(_messages.syncBlockMessages.sourceSyncBlockTooltip) : title ? formatMessage(_messages.syncBlockMessages.referenceSyncBlockTooltip, {
25
- title: title
26
- }) : formatMessage(_messages.syncBlockMessages.defaultSyncBlockTooltip);
85
+ var _useState = (0, _react.useState)(formatMessage(_messages.syncBlockMessages.defaultSyncBlockTooltip)),
86
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
87
+ tooltipContent = _useState2[0],
88
+ setTooltipContent = _useState2[1];
89
+ var tooltipMessage = formatMessage(_messages.syncBlockMessages.defaultSyncBlockTooltip);
90
+ if (isSource) {
91
+ tooltipMessage = formatMessage(_messages.syncBlockMessages.sourceSyncBlockTooltip);
92
+ } else if (title) {
93
+ tooltipMessage = formatMessage(_messages.syncBlockMessages.referenceSyncBlockTooltip, {
94
+ title: title
95
+ });
96
+ }
97
+ var updateTooltipContent = (0, _react.useCallback)(function () {
98
+ if (!(0, _platformFeatureFlags.fg)('platform_synced_block_dogfooding')) {
99
+ return;
100
+ }
101
+ var tooltipContent = tooltipMessage;
102
+ if (contentUpdatedAt) {
103
+ var elapsedTime = formatElapsedTime(contentUpdatedAt, intl);
104
+ tooltipContent = /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_compiled.Text, {
105
+ size: "small",
106
+ color: "color.text.inverse"
107
+ }, tooltipMessage), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement(_compiled.Text, {
108
+ size: "small",
109
+ color: "color.text.inverse",
110
+ weight: "bold"
111
+ }, formatMessage(_messages.syncBlockMessages.referenceSyncBlockLastEdited)), /*#__PURE__*/_react.default.createElement(_compiled.Text, {
112
+ size: "small",
113
+ color: "color.text.inverse"
114
+ }, elapsedTime));
115
+ }
116
+ setTooltipContent(tooltipContent);
117
+ }, [contentUpdatedAt, formatMessage, intl, tooltipMessage]);
27
118
  var ariaDescribedById = "sync-block-label-description-".concat(localId);
28
119
  return /*#__PURE__*/_react.default.createElement(_tooltip.default, {
29
120
  position: "top",
30
- content: tooltipContent
121
+ content: (0, _platformFeatureFlags.fg)('platform_synced_block_dogfooding') ? tooltipContent : tooltipMessage
31
122
  // workaround because tooltip adds aria-describedby with a new id every time the tooltip is opened
32
123
  // this causes an infinite rerender loop because of the forwardRef from the node view we are inside in bodiedSyncBlock
33
124
  // tooltip content is available for screen readers in visually hidden content after the label
34
125
  ,
35
126
  isScreenReaderAnnouncementDisabled: true
127
+ // using this to ensure that the 'last edited' time is updated when the tooltip is opened
128
+ ,
129
+ onShow: updateTooltipContent
36
130
  }, /*#__PURE__*/_react.default.createElement("div", {
37
131
  "data-testid": SyncBlockLabelDataId
38
132
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
@@ -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
  };
@@ -10,22 +10,26 @@ var _syncBlock = require("@atlaskit/editor-common/sync-block");
10
10
  var _SyncBlockLabel = require("./SyncBlockLabel");
11
11
  var SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
12
12
  var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperComponent(_ref) {
13
+ var _syncBlockFetchResult;
13
14
  var syncedBlockRenderer = _ref.syncedBlockRenderer,
14
15
  useFetchSyncBlockData = _ref.useFetchSyncBlockData,
15
- localId = _ref.localId,
16
16
  useFetchSyncBlockTitle = _ref.useFetchSyncBlockTitle,
17
+ localId = _ref.localId,
17
18
  api = _ref.api;
19
+ var syncBlockFetchResult = useFetchSyncBlockData();
20
+ var contentUpdatedAt = syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult === void 0 || (_syncBlockFetchResult = _syncBlockFetchResult.data) === null || _syncBlockFetchResult === void 0 ? void 0 : _syncBlockFetchResult.contentUpdatedAt;
18
21
  return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("div", {
19
22
  "data-testid": SyncBlockRendererWrapperDataId
20
23
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
21
24
  ,
22
25
  className: _syncBlock.SyncBlockSharedCssClassName.renderer
23
26
  }, syncedBlockRenderer({
24
- useFetchSyncBlockData: useFetchSyncBlockData,
27
+ syncBlockFetchResult: syncBlockFetchResult,
25
28
  api: api
26
29
  })), /*#__PURE__*/_react.default.createElement(_SyncBlockLabel.SyncBlockLabel, {
27
30
  isSource: false,
28
31
  useFetchSyncBlockTitle: useFetchSyncBlockTitle,
32
+ contentUpdatedAt: contentUpdatedAt,
29
33
  localId: localId
30
34
  }));
31
35
  };
@@ -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,33 +1,122 @@
1
- import React from 'react';
1
+ import React, { useCallback, useState } from 'react';
2
+ import isYesterday from 'date-fns/isYesterday';
2
3
  import { useIntl } from 'react-intl-next';
3
4
  import { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
4
5
  import { SyncBlockLabelSharedCssClassName } from '@atlaskit/editor-common/sync-block';
5
6
  import BlockSyncedIcon from '@atlaskit/icon-lab/core/block-synced';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
6
8
  import { Text } from '@atlaskit/primitives/compiled';
7
9
  import Tooltip from '@atlaskit/tooltip';
8
10
  import VisuallyHidden from '@atlaskit/visually-hidden';
9
11
  const SyncBlockLabelDataId = 'sync-block-label';
12
+ const SECONDS_IN_MINUTE = 60;
13
+ const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
14
+ const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
15
+ const SECONDS_IN_WEEK = SECONDS_IN_DAY * 7;
16
+ const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30;
17
+ const SECONDS_IN_YEAR = SECONDS_IN_DAY * 365;
18
+ export const formatElapsedTime = (isoDate, intl) => {
19
+ const now = Date.now();
20
+ const date = new Date(isoDate).getTime();
21
+ const diffInSeconds = Math.floor((now - date) / 1000);
22
+ const dateObj = new Date(isoDate);
23
+
24
+ // Show "yesterday" when timestamp is from the previous calendar day
25
+ if (isYesterday(dateObj) && diffInSeconds >= SECONDS_IN_DAY) {
26
+ return intl.formatRelativeTime(-1, 'day', {
27
+ numeric: 'auto',
28
+ style: 'long'
29
+ });
30
+ }
31
+ if (diffInSeconds < SECONDS_IN_MINUTE) {
32
+ return intl.formatRelativeTime(-Math.max(diffInSeconds, 1), 'second', {
33
+ style: 'long'
34
+ });
35
+ } else if (diffInSeconds < SECONDS_IN_HOUR) {
36
+ const minutes = Math.floor(diffInSeconds / SECONDS_IN_MINUTE);
37
+ return intl.formatRelativeTime(-minutes, 'minute', {
38
+ style: 'long'
39
+ });
40
+ } else if (diffInSeconds < SECONDS_IN_DAY) {
41
+ const hours = Math.floor(diffInSeconds / SECONDS_IN_HOUR);
42
+ return intl.formatRelativeTime(-hours, 'hour', {
43
+ style: 'long'
44
+ });
45
+ } else if (diffInSeconds < SECONDS_IN_WEEK) {
46
+ const days = Math.floor(diffInSeconds / SECONDS_IN_DAY);
47
+ return intl.formatRelativeTime(-days, 'day', {
48
+ style: 'long'
49
+ });
50
+ } else if (diffInSeconds < SECONDS_IN_MONTH) {
51
+ const weeks = Math.floor(diffInSeconds / SECONDS_IN_WEEK);
52
+ return intl.formatRelativeTime(-weeks, 'week', {
53
+ style: 'long'
54
+ });
55
+ } else if (diffInSeconds < SECONDS_IN_YEAR) {
56
+ const months = Math.floor(diffInSeconds / SECONDS_IN_MONTH);
57
+ return intl.formatRelativeTime(-months, 'month', {
58
+ style: 'long'
59
+ });
60
+ } else {
61
+ const years = Math.floor(diffInSeconds / SECONDS_IN_YEAR);
62
+ return intl.formatRelativeTime(-years, 'year', {
63
+ style: 'long'
64
+ });
65
+ }
66
+ };
10
67
  const SyncBlockLabelComponent = ({
11
68
  isSource,
12
69
  useFetchSyncBlockTitle,
13
- localId
70
+ localId,
71
+ contentUpdatedAt
14
72
  }) => {
73
+ const intl = useIntl();
15
74
  const {
16
75
  formatMessage
17
- } = useIntl();
76
+ } = intl;
18
77
  const title = useFetchSyncBlockTitle === null || useFetchSyncBlockTitle === void 0 ? void 0 : useFetchSyncBlockTitle();
19
- const tooltipContent = isSource ? formatMessage(messages.sourceSyncBlockTooltip) : title ? formatMessage(messages.referenceSyncBlockTooltip, {
20
- title
21
- }) : formatMessage(messages.defaultSyncBlockTooltip);
78
+ const [tooltipContent, setTooltipContent] = useState(formatMessage(messages.defaultSyncBlockTooltip));
79
+ let tooltipMessage = formatMessage(messages.defaultSyncBlockTooltip);
80
+ if (isSource) {
81
+ tooltipMessage = formatMessage(messages.sourceSyncBlockTooltip);
82
+ } else if (title) {
83
+ tooltipMessage = formatMessage(messages.referenceSyncBlockTooltip, {
84
+ title
85
+ });
86
+ }
87
+ const updateTooltipContent = useCallback(() => {
88
+ if (!fg('platform_synced_block_dogfooding')) {
89
+ return;
90
+ }
91
+ let tooltipContent = tooltipMessage;
92
+ if (contentUpdatedAt) {
93
+ const elapsedTime = formatElapsedTime(contentUpdatedAt, intl);
94
+ tooltipContent = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Text, {
95
+ size: "small",
96
+ color: "color.text.inverse"
97
+ }, tooltipMessage), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement(Text, {
98
+ size: "small",
99
+ color: "color.text.inverse",
100
+ weight: "bold"
101
+ }, formatMessage(messages.referenceSyncBlockLastEdited)), /*#__PURE__*/React.createElement(Text, {
102
+ size: "small",
103
+ color: "color.text.inverse"
104
+ }, elapsedTime));
105
+ }
106
+ setTooltipContent(tooltipContent);
107
+ }, [contentUpdatedAt, formatMessage, intl, tooltipMessage]);
22
108
  const ariaDescribedById = `sync-block-label-description-${localId}`;
23
109
  return /*#__PURE__*/React.createElement(Tooltip, {
24
110
  position: "top",
25
- content: tooltipContent
111
+ content: fg('platform_synced_block_dogfooding') ? tooltipContent : tooltipMessage
26
112
  // workaround because tooltip adds aria-describedby with a new id every time the tooltip is opened
27
113
  // this causes an infinite rerender loop because of the forwardRef from the node view we are inside in bodiedSyncBlock
28
114
  // tooltip content is available for screen readers in visually hidden content after the label
29
115
  ,
30
116
  isScreenReaderAnnouncementDisabled: true
117
+ // using this to ensure that the 'last edited' time is updated when the tooltip is opened
118
+ ,
119
+ onShow: updateTooltipContent
31
120
  }, /*#__PURE__*/React.createElement("div", {
32
121
  "data-testid": SyncBlockLabelDataId
33
122
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop