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

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 (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/afm-cc/tsconfig.json +4 -1
  3. package/dist/cjs/editor-commands/index.js +4 -0
  4. package/dist/cjs/pm-plugins/main.js +8 -1
  5. package/dist/cjs/pm-plugins/utils/handle-bodied-sync-block-removal.js +2 -2
  6. package/dist/cjs/ui/DeleteConfirmationModal.compiled.css +2 -0
  7. package/dist/cjs/ui/DeleteConfirmationModal.js +151 -13
  8. package/dist/cjs/ui/SyncBlockLabel.js +44 -94
  9. package/dist/cjs/ui/SyncBlockRendererWrapper.js +2 -1
  10. package/dist/cjs/ui/SyncedLocationDropdown.js +9 -3
  11. package/dist/cjs/ui/floating-toolbar.js +2 -2
  12. package/dist/cjs/ui/utils/time.js +63 -0
  13. package/dist/es2019/editor-commands/index.js +4 -0
  14. package/dist/es2019/pm-plugins/main.js +8 -1
  15. package/dist/es2019/pm-plugins/utils/handle-bodied-sync-block-removal.js +2 -2
  16. package/dist/es2019/ui/DeleteConfirmationModal.compiled.css +2 -0
  17. package/dist/es2019/ui/DeleteConfirmationModal.js +121 -12
  18. package/dist/es2019/ui/SyncBlockLabel.js +18 -72
  19. package/dist/es2019/ui/SyncBlockRendererWrapper.js +2 -1
  20. package/dist/es2019/ui/SyncedLocationDropdown.js +9 -3
  21. package/dist/es2019/ui/floating-toolbar.js +2 -2
  22. package/dist/es2019/ui/utils/time.js +56 -0
  23. package/dist/esm/editor-commands/index.js +4 -0
  24. package/dist/esm/pm-plugins/main.js +8 -1
  25. package/dist/esm/pm-plugins/utils/handle-bodied-sync-block-removal.js +2 -2
  26. package/dist/esm/ui/DeleteConfirmationModal.compiled.css +2 -0
  27. package/dist/esm/ui/DeleteConfirmationModal.js +152 -14
  28. package/dist/esm/ui/SyncBlockLabel.js +42 -92
  29. package/dist/esm/ui/SyncBlockRendererWrapper.js +2 -1
  30. package/dist/esm/ui/SyncedLocationDropdown.js +9 -3
  31. package/dist/esm/ui/floating-toolbar.js +2 -2
  32. package/dist/esm/ui/utils/time.js +56 -0
  33. package/dist/types/pm-plugins/utils/handle-bodied-sync-block-removal.d.ts +2 -2
  34. package/dist/types/ui/SyncBlockLabel.d.ts +2 -4
  35. package/dist/types/ui/SyncBlockRendererWrapper.d.ts +1 -1
  36. package/dist/types/ui/utils/time.d.ts +2 -0
  37. package/dist/types-ts4.5/pm-plugins/utils/handle-bodied-sync-block-removal.d.ts +2 -2
  38. package/dist/types-ts4.5/ui/SyncBlockLabel.d.ts +2 -4
  39. package/dist/types-ts4.5/ui/SyncBlockRendererWrapper.d.ts +1 -1
  40. package/dist/types-ts4.5/ui/utils/time.d.ts +2 -0
  41. package/package.json +3 -3
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.formatElapsedTime = void 0;
8
+ var _isYesterday = _interopRequireDefault(require("date-fns/isYesterday"));
9
+ var SECONDS_IN_MINUTE = 60;
10
+ var SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
11
+ var SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
12
+ var SECONDS_IN_WEEK = SECONDS_IN_DAY * 7;
13
+ var SECONDS_IN_MONTH = SECONDS_IN_DAY * 30;
14
+ var SECONDS_IN_YEAR = SECONDS_IN_DAY * 365;
15
+ var formatElapsedTime = exports.formatElapsedTime = function formatElapsedTime(isoDate, intl) {
16
+ var now = Date.now();
17
+ var date = new Date(isoDate).getTime();
18
+ var diffInSeconds = Math.floor((now - date) / 1000);
19
+ var dateObj = new Date(isoDate);
20
+
21
+ // Show "yesterday" when timestamp is from the previous calendar day
22
+ if ((0, _isYesterday.default)(dateObj) && diffInSeconds >= SECONDS_IN_DAY) {
23
+ return intl.formatRelativeTime(-1, 'day', {
24
+ numeric: 'auto',
25
+ style: 'long'
26
+ });
27
+ }
28
+ if (diffInSeconds < SECONDS_IN_MINUTE) {
29
+ return intl.formatRelativeTime(-Math.max(diffInSeconds, 1), 'second', {
30
+ style: 'long'
31
+ });
32
+ } else if (diffInSeconds < SECONDS_IN_HOUR) {
33
+ var minutes = Math.floor(diffInSeconds / SECONDS_IN_MINUTE);
34
+ return intl.formatRelativeTime(-minutes, 'minute', {
35
+ style: 'long'
36
+ });
37
+ } else if (diffInSeconds < SECONDS_IN_DAY) {
38
+ var hours = Math.floor(diffInSeconds / SECONDS_IN_HOUR);
39
+ return intl.formatRelativeTime(-hours, 'hour', {
40
+ style: 'long'
41
+ });
42
+ } else if (diffInSeconds < SECONDS_IN_WEEK) {
43
+ var days = Math.floor(diffInSeconds / SECONDS_IN_DAY);
44
+ return intl.formatRelativeTime(-days, 'day', {
45
+ style: 'long'
46
+ });
47
+ } else if (diffInSeconds < SECONDS_IN_MONTH) {
48
+ var weeks = Math.floor(diffInSeconds / SECONDS_IN_WEEK);
49
+ return intl.formatRelativeTime(-weeks, 'week', {
50
+ style: 'long'
51
+ });
52
+ } else if (diffInSeconds < SECONDS_IN_YEAR) {
53
+ var months = Math.floor(diffInSeconds / SECONDS_IN_MONTH);
54
+ return intl.formatRelativeTime(-months, 'month', {
55
+ style: 'long'
56
+ });
57
+ } else {
58
+ var years = Math.floor(diffInSeconds / SECONDS_IN_YEAR);
59
+ return intl.formatRelativeTime(-years, 'year', {
60
+ style: 'long'
61
+ });
62
+ }
63
+ };
@@ -197,6 +197,10 @@ export const unsync = (storeManager, isBodiedSyncBlock, view) => {
197
197
  return false;
198
198
  }
199
199
  if (isBodiedSyncBlock) {
200
+ const content = syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.node.content;
201
+ const tr = state.tr;
202
+ tr.replaceWith(syncBlock.pos, syncBlock.pos + syncBlock.node.nodeSize, content).setMeta('deletionReason', 'source-block-unsynced');
203
+ view.dispatch(tr);
200
204
  return true;
201
205
  }
202
206
 
@@ -31,6 +31,13 @@ const showCopiedFlag = api => {
31
31
  });
32
32
  }, 0);
33
33
  };
34
+ const getDeleteReason = tr => {
35
+ const reason = tr.getMeta('deletionReason');
36
+ if (!reason) {
37
+ return 'source-block-deleted';
38
+ }
39
+ return reason;
40
+ };
34
41
  export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api) => {
35
42
  const {
36
43
  useLongPressSelection = false
@@ -199,7 +206,7 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
199
206
  if (!isOffline) {
200
207
  if (bodiedSyncBlockRemoved.length > 0) {
201
208
  confirmationTransactionRef.current = tr;
202
- return handleBodiedSyncBlockRemoval(tr, bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef);
209
+ return handleBodiedSyncBlockRemoval(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, fg('platform_synced_block_dogfooding') ? getDeleteReason(tr) : undefined);
203
210
  }
204
211
  if (bodiedSyncBlockAdded.length > 0) {
205
212
  if (Boolean(tr.getMeta(pmHistoryPluginKey))) {
@@ -19,7 +19,7 @@ const onDismissed = syncBlockStore => tr => {
19
19
  bodiedSyncBlockDeletionStatus: 'none'
20
20
  });
21
21
  };
22
- export const handleBodiedSyncBlockRemoval = (tr, bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef) => {
22
+ export const handleBodiedSyncBlockRemoval = (bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, deletionReason) => {
23
23
  // Clear potential old pending deletion to retreat the deletion as first attempt
24
24
  syncBlockStore.sourceManager.clearPendingDeletion();
25
25
 
@@ -27,7 +27,7 @@ export const handleBodiedSyncBlockRemoval = (tr, bodiedSyncBlockRemoved, syncBlo
27
27
  // we block the transaction here, and wait for user confirmation to proceed with deletion.
28
28
  // See editor-common/src/sync-block/sync-block-store-manager.ts for how we handle user confirmation and
29
29
  // proceed with deletion.
30
- syncBlockStore.sourceManager.deleteSyncBlocksWithConfirmation(bodiedSyncBlockRemoved.map(node => node.attrs), () => {
30
+ syncBlockStore.sourceManager.deleteSyncBlocksWithConfirmation(bodiedSyncBlockRemoved.map(node => node.attrs), deletionReason, () => {
31
31
  var _api$core2;
32
32
  const confirmationTransaction = confirmationTransactionRef.current;
33
33
  if (!confirmationTransaction) {
@@ -0,0 +1,2 @@
1
+ ._195g1wug{margin-inline:auto}
2
+ ._1mou1wug{margin-block:auto}
@@ -1,3 +1,6 @@
1
+ /* DeleteConfirmationModal.tsx generated by @compiled/babel-plugin v0.38.1 */
2
+ import "./DeleteConfirmationModal.compiled.css";
3
+ import { ax, ix } from "@compiled/react/runtime";
1
4
  import React, { useCallback, useEffect, useState } from 'react';
2
5
  import { useIntl } from 'react-intl-next';
3
6
  import Button from '@atlaskit/button/new';
@@ -6,15 +9,38 @@ import { syncBlockMessages as messages } from '@atlaskit/editor-common/messages'
6
9
  import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
7
10
  import ModalDialog, { ModalBody, ModalFooter, ModalHeader, ModalTitle, ModalTransition } from '@atlaskit/modal-dialog';
8
11
  import { fg } from '@atlaskit/platform-feature-flags';
9
- import { Text } from '@atlaskit/primitives/compiled';
12
+ import { Text, Box } from '@atlaskit/primitives/compiled';
13
+ import Spinner from '@atlaskit/spinner';
10
14
  import { syncedBlockPluginKey } from '../pm-plugins/main';
15
+ const modalContentMap = {
16
+ 'source-block-deleted': {
17
+ titleMultiple: messages.deleteConfirmationModalTitleSingle,
18
+ titleSingle: messages.deleteConfirmationModalTitleSingle,
19
+ descriptionSingle: messages.deleteConfirmationModalDescriptionNoRef,
20
+ descriptionMultiple: messages.deleteConfirmationModalDescription,
21
+ confirmButtonLabel: messages.deleteConfirmationModalDeleteButton
22
+ },
23
+ 'source-block-unsynced': {
24
+ titleMultiple: messages.unsyncConfirmationModalTitle,
25
+ titleSingle: messages.unsyncConfirmationModalTitle,
26
+ descriptionSingle: messages.unsyncConfirmationModalDescriptionSingle,
27
+ descriptionMultiple: messages.unsyncConfirmationModalDescriptionMultiple,
28
+ confirmButtonLabel: messages.deleteConfirmationModalUnsyncButton
29
+ }
30
+ };
31
+ const styles = {
32
+ spinner: "_1mou1wug _195g1wug"
33
+ };
11
34
  export const DeleteConfirmationModal = ({
12
35
  syncBlockStoreManager,
13
36
  api
14
37
  }) => {
15
- var _api$core2, _api$core4, _api$core6;
38
+ var _api$core2, _api$core4, _api$core6, _syncBlockIds$length;
16
39
  const [isOpen, setIsOpen] = useState(false);
17
- const [syncBlockCount, setSyncBlockCount] = useState(1);
40
+ const [syncBlockIds, setSyncBlockIds] = useState(undefined);
41
+ const [referenceCount, setReferenceCount] = useState(undefined);
42
+ const [deleteReason, setDeleteReason] = useState('source-block-deleted');
43
+ const [fetchStatus, setFetchStatus] = useState('none');
18
44
  const {
19
45
  mode,
20
46
  bodiedSyncBlockDeletionStatus,
@@ -39,6 +65,8 @@ export const DeleteConfirmationModal = ({
39
65
  }
40
66
  if (!confirm) {
41
67
  setIsOpen(false);
68
+ setFetchStatus('none');
69
+ setReferenceCount(undefined);
42
70
  }
43
71
  api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({
44
72
  tr
@@ -49,9 +77,12 @@ export const DeleteConfirmationModal = ({
49
77
  });
50
78
  });
51
79
  }, [api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions]);
52
- const confirmationCallback = useCallback(syncBlockCount => {
80
+ const confirmationCallback = useCallback((syncBlockIds, deleteReason) => {
53
81
  setIsOpen(true);
54
- setSyncBlockCount(syncBlockCount);
82
+ setSyncBlockIds(syncBlockIds);
83
+ if (deleteReason) {
84
+ setDeleteReason(deleteReason);
85
+ }
55
86
  const confirmedPromise = new Promise(resolve => {
56
87
  resolverRef.current = resolve;
57
88
  });
@@ -79,6 +110,7 @@ export const DeleteConfirmationModal = ({
79
110
  var _api$core5;
80
111
  // auto close modal once deletion is successful
81
112
  setIsOpen(false);
113
+ setFetchStatus('none');
82
114
  api === null || api === void 0 ? void 0 : (_api$core5 = api.core) === null || _api$core5 === void 0 ? void 0 : _api$core5.actions.execute(({
83
115
  tr
84
116
  }) => {
@@ -89,15 +121,55 @@ export const DeleteConfirmationModal = ({
89
121
  });
90
122
  }
91
123
  }, [api === null || api === void 0 ? void 0 : (_api$core6 = api.core) === null || _api$core6 === void 0 ? void 0 : _api$core6.actions, bodiedSyncBlockDeletionStatus, isOpen]);
124
+ useEffect(() => {
125
+ if (isOpen && syncBlockIds !== undefined && fg('platform_synced_block_dogfooding')) {
126
+ let referenceCount = 0;
127
+ setFetchStatus('loading');
128
+ let fetchFailed = false;
129
+ syncBlockIds.forEach(async syncBlockId => {
130
+ if (fetchFailed) {
131
+ return;
132
+ }
133
+ const references = await syncBlockStoreManager.sourceManager.fetchReferences(syncBlockId.resourceId);
134
+ if (references.error) {
135
+ // Consider fetch fails as soon as one of the fetches fails
136
+ setFetchStatus('error');
137
+ fetchFailed = true;
138
+ return;
139
+ } else {
140
+ var _references$reference, _references$reference2;
141
+ referenceCount += (_references$reference = (_references$reference2 = references.references) === null || _references$reference2 === void 0 ? void 0 : _references$reference2.length) !== null && _references$reference !== void 0 ? _references$reference : 0;
142
+ }
143
+ });
144
+ if (!fetchFailed) {
145
+ setReferenceCount(referenceCount);
146
+ setFetchStatus('success');
147
+ }
148
+ }
149
+ }, [isOpen, syncBlockIds, syncBlockStoreManager.sourceManager]);
92
150
  return /*#__PURE__*/React.createElement(ModalTransition, null, isOpen && /*#__PURE__*/React.createElement(ModalDialog, {
93
151
  onClose: handleClick(false),
94
- testId: "sync-block-delete-confirmation"
95
- }, /*#__PURE__*/React.createElement(ModalHeader, {
152
+ testId: "sync-block-delete-confirmation",
153
+ height: 184
154
+ }, fg('platform_synced_block_dogfooding') ? /*#__PURE__*/React.createElement(React.Fragment, null, referenceCount === undefined ? /*#__PURE__*/React.createElement(Box, {
155
+ xcss: styles.spinner
156
+ }, /*#__PURE__*/React.createElement(Spinner, {
157
+ size: "large"
158
+ })) : /*#__PURE__*/React.createElement(ModalContent, {
159
+ content: modalContentMap[deleteReason],
160
+ referenceCount: referenceCount,
161
+ handleClick: handleClick,
162
+ formatMessage: formatMessage,
163
+ isDeleting: bodiedSyncBlockDeletionStatus === 'processing',
164
+ isDisabled: isOfflineMode(mode),
165
+ deleteReason: deleteReason,
166
+ failToFetch: fetchStatus === 'error'
167
+ })) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ModalHeader, {
96
168
  hasCloseButton: true
97
169
  }, /*#__PURE__*/React.createElement(ModalTitle, {
98
170
  appearance: "warning"
99
- }, formatMessage(messages.deleteConfirmationModalTitle))), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement(Text, null, formatMessage(messages.deleteConfirmationModalDescription, {
100
- syncBlockCount
171
+ }, formatMessage(messages.deleteConfirmationModalTitleSingle))), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement(Text, null, formatMessage(messages.deleteConfirmationModalDescription, {
172
+ syncBlockCount: (_syncBlockIds$length = syncBlockIds === null || syncBlockIds === void 0 ? void 0 : syncBlockIds.length) !== null && _syncBlockIds$length !== void 0 ? _syncBlockIds$length : 1
101
173
  }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
102
174
  appearance: "subtle",
103
175
  onClick: handleClick(false)
@@ -106,7 +178,44 @@ export const DeleteConfirmationModal = ({
106
178
  onClick: handleClick(true),
107
179
  autoFocus: true,
108
180
  isDisabled: isOfflineMode(mode),
109
- isLoading: bodiedSyncBlockDeletionStatus === 'processing',
110
- testId: fg('platform_synced_block_dogfooding') ? 'synced-block-delete-confirmation-modal-delete-button' : undefined
111
- }, formatMessage(messages.deleteConfirmationModalDeleteButton)))));
181
+ isLoading: bodiedSyncBlockDeletionStatus === 'processing'
182
+ }, formatMessage(messages.deleteConfirmationModalDeleteButton))))));
183
+ };
184
+ const ModalContent = ({
185
+ content,
186
+ referenceCount,
187
+ handleClick,
188
+ formatMessage,
189
+ isDeleting,
190
+ isDisabled,
191
+ deleteReason,
192
+ failToFetch
193
+ }) => {
194
+ const {
195
+ titleMultiple,
196
+ titleSingle,
197
+ descriptionSingle,
198
+ descriptionMultiple,
199
+ confirmButtonLabel
200
+ } = content;
201
+ const hasNoReferenceOrFailToFetch = referenceCount === 0 || failToFetch;
202
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ModalHeader, {
203
+ hasCloseButton: true
204
+ }, /*#__PURE__*/React.createElement(ModalTitle, {
205
+ appearance: "warning"
206
+ }, hasNoReferenceOrFailToFetch ? formatMessage(titleSingle) : formatMessage(titleMultiple, {
207
+ count: referenceCount
208
+ }))), /*#__PURE__*/React.createElement(ModalBody, null, /*#__PURE__*/React.createElement(Text, null, hasNoReferenceOrFailToFetch ? formatMessage(descriptionSingle) : formatMessage(descriptionMultiple, {
209
+ syncBlockCount: referenceCount
210
+ }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
211
+ appearance: "subtle",
212
+ onClick: handleClick(false)
213
+ }, formatMessage(messages.deleteConfirmationModalCancelButton)), /*#__PURE__*/React.createElement(Button, {
214
+ appearance: "warning",
215
+ onClick: handleClick(true),
216
+ autoFocus: true,
217
+ isDisabled: isDisabled,
218
+ isLoading: isDeleting,
219
+ testId: `synced-block-delete-confirmation-modal-${deleteReason}-button`
220
+ }, formatMessage(confirmButtonLabel))));
112
221
  };
@@ -1,5 +1,4 @@
1
1
  import React, { useCallback, useState } from 'react';
2
- import isYesterday from 'date-fns/isYesterday';
3
2
  import { useIntl } from 'react-intl-next';
4
3
  import { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
5
4
  import { SyncBlockLabelSharedCssClassName } from '@atlaskit/editor-common/sync-block';
@@ -8,76 +7,21 @@ import { fg } from '@atlaskit/platform-feature-flags';
8
7
  import { Text } from '@atlaskit/primitives/compiled';
9
8
  import Tooltip from '@atlaskit/tooltip';
10
9
  import VisuallyHidden from '@atlaskit/visually-hidden';
10
+ import { formatElapsedTime } from './utils/time';
11
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
- };
67
12
  const SyncBlockLabelComponent = ({
13
+ contentUpdatedAt,
68
14
  isSource,
69
- useFetchSyncBlockTitle,
70
15
  localId,
71
- contentUpdatedAt
16
+ title
72
17
  }) => {
73
18
  const intl = useIntl();
74
19
  const {
75
20
  formatMessage
76
21
  } = intl;
77
- const title = useFetchSyncBlockTitle === null || useFetchSyncBlockTitle === void 0 ? void 0 : useFetchSyncBlockTitle();
78
22
  const [tooltipContent, setTooltipContent] = useState(formatMessage(messages.defaultSyncBlockTooltip));
79
23
  let tooltipMessage = formatMessage(messages.defaultSyncBlockTooltip);
80
- if (isSource) {
24
+ if (isSource && !fg('platform_synced_block_dogfooding')) {
81
25
  tooltipMessage = formatMessage(messages.sourceSyncBlockTooltip);
82
26
  } else if (title) {
83
27
  tooltipMessage = formatMessage(messages.referenceSyncBlockTooltip, {
@@ -106,18 +50,7 @@ const SyncBlockLabelComponent = ({
106
50
  setTooltipContent(tooltipContent);
107
51
  }, [contentUpdatedAt, formatMessage, intl, tooltipMessage]);
108
52
  const ariaDescribedById = `sync-block-label-description-${localId}`;
109
- return /*#__PURE__*/React.createElement(Tooltip, {
110
- position: "top",
111
- content: fg('platform_synced_block_dogfooding') ? tooltipContent : tooltipMessage
112
- // workaround because tooltip adds aria-describedby with a new id every time the tooltip is opened
113
- // this causes an infinite rerender loop because of the forwardRef from the node view we are inside in bodiedSyncBlock
114
- // tooltip content is available for screen readers in visually hidden content after the label
115
- ,
116
- isScreenReaderAnnouncementDisabled: true
117
- // using this to ensure that the 'last edited' time is updated when the tooltip is opened
118
- ,
119
- onShow: updateTooltipContent
120
- }, /*#__PURE__*/React.createElement("div", {
53
+ const LabelComponent = () => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
121
54
  "data-testid": SyncBlockLabelDataId
122
55
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
123
56
  ,
@@ -137,5 +70,18 @@ const SyncBlockLabelComponent = ({
137
70
  }, title)), /*#__PURE__*/React.createElement(VisuallyHidden, {
138
71
  id: ariaDescribedById
139
72
  }, tooltipContent));
73
+ const LabelWithTooltip = () => /*#__PURE__*/React.createElement(Tooltip, {
74
+ position: "top",
75
+ content: fg('platform_synced_block_dogfooding') ? tooltipContent : tooltipMessage
76
+ // workaround because tooltip adds aria-describedby with a new id every time the tooltip is opened
77
+ // this causes an infinite rerender loop because of the forwardRef from the node view we are inside in bodiedSyncBlock
78
+ // tooltip content is available for screen readers in visually hidden content after the label
79
+ ,
80
+ isScreenReaderAnnouncementDisabled: true
81
+ // using this to ensure that the 'last edited' time is updated when the tooltip is opened
82
+ ,
83
+ onShow: updateTooltipContent
84
+ }, /*#__PURE__*/React.createElement(LabelComponent, null));
85
+ return fg('platform_synced_block_dogfooding') ? isSource ? /*#__PURE__*/React.createElement(LabelComponent, null) : /*#__PURE__*/React.createElement(LabelWithTooltip, null) : /*#__PURE__*/React.createElement(LabelWithTooltip, null);
140
86
  };
141
87
  export const SyncBlockLabel = /*#__PURE__*/React.memo(SyncBlockLabelComponent);
@@ -11,6 +11,7 @@ const SyncBlockRendererWrapperComponent = ({
11
11
  }) => {
12
12
  var _syncBlockFetchResult, _syncBlockFetchResult2;
13
13
  const syncBlockFetchResult = useFetchSyncBlockData();
14
+ const title = useFetchSyncBlockTitle === null || useFetchSyncBlockTitle === void 0 ? void 0 : useFetchSyncBlockTitle();
14
15
  const contentUpdatedAt = syncBlockFetchResult === null || syncBlockFetchResult === void 0 ? void 0 : (_syncBlockFetchResult = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult === void 0 ? void 0 : (_syncBlockFetchResult2 = _syncBlockFetchResult.data) === null || _syncBlockFetchResult2 === void 0 ? void 0 : _syncBlockFetchResult2.contentUpdatedAt;
15
16
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
16
17
  "data-testid": SyncBlockRendererWrapperDataId
@@ -22,7 +23,7 @@ const SyncBlockRendererWrapperComponent = ({
22
23
  api
23
24
  })), /*#__PURE__*/React.createElement(SyncBlockLabel, {
24
25
  isSource: false,
25
- useFetchSyncBlockTitle: useFetchSyncBlockTitle,
26
+ title: title,
26
27
  contentUpdatedAt: contentUpdatedAt,
27
28
  localId: localId
28
29
  }));
@@ -4,6 +4,7 @@ import "./SyncedLocationDropdown.compiled.css";
4
4
  import * as React from 'react';
5
5
  import { ax, ix } from "@compiled/react/runtime";
6
6
  import { useState, useEffect } from 'react';
7
+ import { cx } from '@compiled/react';
7
8
  import DropdownMenu, { DropdownItem, DropdownItemGroup } from '@atlaskit/dropdown-menu';
8
9
  import { syncBlockMessages as messages } from '@atlaskit/editor-common/messages';
9
10
  import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui';
@@ -29,13 +30,18 @@ const styles = {
29
30
  note: "_syaz1rpy _o5721q9c",
30
31
  lozenge: "_ahbq12x7 _1ul91wqb",
31
32
  noResultsContainer: "_1bsbo8uj _y3gn1h6o",
32
- dropdownContent: "_1rjcv77o _1bsbsmdz _1tkeqkoa _c71lko4j _1e0c1txw _1bah1h6o _4cvr1h6o",
33
+ dropdownContent: "_1rjcv77o _1bsbsmdz _c71lko4j _1e0c1txw _1bah1h6o _4cvr1h6o",
34
+ containerWithMinHeight: "_1tkeqkoa",
33
35
  contentContainer: "_y44vfmxe _1bsb1osq _1wpz1fhb _18m91wug",
34
36
  errorContainer: "_1bsbo8uj _1e0c1txw",
35
37
  errorIcon: "_1mour5cr",
36
38
  learnMoreLink: "_4bfu1r31 _1hmsglyw _ajmmnqa1",
37
39
  requestAccess: "_1bsb19n7 _o5721q9c _ahbq12x7 _syaz1rpy"
38
40
  };
41
+ const shouldApplyMinHeight = (fetchStatus, itemCount) => {
42
+ // When there are 1/2 items, dropdown height is less than minHeight 144px
43
+ return !(fetchStatus === 'success' && itemCount > 0);
44
+ };
39
45
  const ItemTitle = ({
40
46
  title,
41
47
  formatMessage,
@@ -50,7 +56,7 @@ const ItemTitle = ({
50
56
  }, title), onSameDocument && /*#__PURE__*/React.createElement(Box, {
51
57
  as: "span",
52
58
  xcss: styles.note
53
- }, "\xA0- ", formatMessage(productType === 'confluence-page' ? messages.syncedLocationDropdownTitleNoteForConfluencePage : messages.syncedLocationDropdownTitleNoteForJiraWorkItem)), isSource && /*#__PURE__*/React.createElement(Box, {
59
+ }, "\xA0-", ' ', formatMessage(productType === 'confluence-page' ? messages.syncedLocationDropdownTitleNoteForConfluencePage : messages.syncedLocationDropdownTitleNoteForJiraWorkItem)), isSource && /*#__PURE__*/React.createElement(Box, {
54
60
  as: "span",
55
61
  xcss: styles.lozenge
56
62
  }, /*#__PURE__*/React.createElement(Lozenge, null, formatMessage(messages.syncedLocationDropdownSourceLozenge))), !hasAccess && /*#__PURE__*/React.createElement(Box, {
@@ -245,7 +251,7 @@ const DropdownContent = ({
245
251
  }
246
252
  };
247
253
  return /*#__PURE__*/React.createElement(Box, {
248
- xcss: styles.dropdownContent
254
+ xcss: cx(styles.dropdownContent, shouldApplyMinHeight(fetchStatus, referenceData.length) && styles.containerWithMinHeight)
249
255
  }, content());
250
256
  };
251
257
  const LoadingScreen = () => {
@@ -14,13 +14,13 @@ import { findSyncBlockOrBodiedSyncBlock, isBodiedSyncBlockNode } from '../pm-plu
14
14
  import { SYNCED_BLOCK_BUTTON_TEST_ID } from '../types';
15
15
  import { SyncedLocationDropdown } from './SyncedLocationDropdown';
16
16
  export const getToolbarConfig = (state, intl, api, syncBlockStore) => {
17
- var _api$decorations, _api$connectivity, _api$connectivity$sha;
17
+ var _syncBlockInstance$er, _api$decorations, _api$connectivity, _api$connectivity$sha;
18
18
  const syncBlockObject = findSyncBlockOrBodiedSyncBlock(state.schema, state.selection);
19
19
  if (!syncBlockObject) {
20
20
  return;
21
21
  }
22
22
  const syncBlockInstance = syncBlockStore.referenceManager.getFromCache(syncBlockObject.node.attrs.resourceId);
23
- const isUnsyncedBlock = (syncBlockInstance === null || syncBlockInstance === void 0 ? void 0 : syncBlockInstance.error) === SyncBlockError.NotFound;
23
+ const isUnsyncedBlock = (syncBlockInstance === null || syncBlockInstance === void 0 ? void 0 : (_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.type) === SyncBlockError.NotFound;
24
24
  const isErroredBlock = syncBlockInstance === null || syncBlockInstance === void 0 ? void 0 : syncBlockInstance.error;
25
25
  const {
26
26
  schema: {
@@ -0,0 +1,56 @@
1
+ import isYesterday from 'date-fns/isYesterday';
2
+ const SECONDS_IN_MINUTE = 60;
3
+ const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
4
+ const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
5
+ const SECONDS_IN_WEEK = SECONDS_IN_DAY * 7;
6
+ const SECONDS_IN_MONTH = SECONDS_IN_DAY * 30;
7
+ const SECONDS_IN_YEAR = SECONDS_IN_DAY * 365;
8
+ export const formatElapsedTime = (isoDate, intl) => {
9
+ const now = Date.now();
10
+ const date = new Date(isoDate).getTime();
11
+ const diffInSeconds = Math.floor((now - date) / 1000);
12
+ const dateObj = new Date(isoDate);
13
+
14
+ // Show "yesterday" when timestamp is from the previous calendar day
15
+ if (isYesterday(dateObj) && diffInSeconds >= SECONDS_IN_DAY) {
16
+ return intl.formatRelativeTime(-1, 'day', {
17
+ numeric: 'auto',
18
+ style: 'long'
19
+ });
20
+ }
21
+ if (diffInSeconds < SECONDS_IN_MINUTE) {
22
+ return intl.formatRelativeTime(-Math.max(diffInSeconds, 1), 'second', {
23
+ style: 'long'
24
+ });
25
+ } else if (diffInSeconds < SECONDS_IN_HOUR) {
26
+ const minutes = Math.floor(diffInSeconds / SECONDS_IN_MINUTE);
27
+ return intl.formatRelativeTime(-minutes, 'minute', {
28
+ style: 'long'
29
+ });
30
+ } else if (diffInSeconds < SECONDS_IN_DAY) {
31
+ const hours = Math.floor(diffInSeconds / SECONDS_IN_HOUR);
32
+ return intl.formatRelativeTime(-hours, 'hour', {
33
+ style: 'long'
34
+ });
35
+ } else if (diffInSeconds < SECONDS_IN_WEEK) {
36
+ const days = Math.floor(diffInSeconds / SECONDS_IN_DAY);
37
+ return intl.formatRelativeTime(-days, 'day', {
38
+ style: 'long'
39
+ });
40
+ } else if (diffInSeconds < SECONDS_IN_MONTH) {
41
+ const weeks = Math.floor(diffInSeconds / SECONDS_IN_WEEK);
42
+ return intl.formatRelativeTime(-weeks, 'week', {
43
+ style: 'long'
44
+ });
45
+ } else if (diffInSeconds < SECONDS_IN_YEAR) {
46
+ const months = Math.floor(diffInSeconds / SECONDS_IN_MONTH);
47
+ return intl.formatRelativeTime(-months, 'month', {
48
+ style: 'long'
49
+ });
50
+ } else {
51
+ const years = Math.floor(diffInSeconds / SECONDS_IN_YEAR);
52
+ return intl.formatRelativeTime(-years, 'year', {
53
+ style: 'long'
54
+ });
55
+ }
56
+ };
@@ -187,6 +187,10 @@ export var unsync = function unsync(storeManager, isBodiedSyncBlock, view) {
187
187
  return false;
188
188
  }
189
189
  if (isBodiedSyncBlock) {
190
+ var content = syncBlock === null || syncBlock === void 0 ? void 0 : syncBlock.node.content;
191
+ var tr = state.tr;
192
+ tr.replaceWith(syncBlock.pos, syncBlock.pos + syncBlock.node.nodeSize, content).setMeta('deletionReason', 'source-block-unsynced');
193
+ view.dispatch(tr);
190
194
  return true;
191
195
  }
192
196
 
@@ -36,6 +36,13 @@ var showCopiedFlag = function showCopiedFlag(api) {
36
36
  });
37
37
  }, 0);
38
38
  };
39
+ var getDeleteReason = function getDeleteReason(tr) {
40
+ var reason = tr.getMeta('deletionReason');
41
+ if (!reason) {
42
+ return 'source-block-deleted';
43
+ }
44
+ return reason;
45
+ };
39
46
  export var createPlugin = function createPlugin(options, pmPluginFactoryParams, syncBlockStore, api) {
40
47
  var _ref2 = options || {},
41
48
  _ref2$useLongPressSel = _ref2.useLongPressSelection,
@@ -199,7 +206,7 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
199
206
  if (!isOffline) {
200
207
  if (bodiedSyncBlockRemoved.length > 0) {
201
208
  confirmationTransactionRef.current = tr;
202
- return handleBodiedSyncBlockRemoval(tr, bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef);
209
+ return handleBodiedSyncBlockRemoval(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, fg('platform_synced_block_dogfooding') ? getDeleteReason(tr) : undefined);
203
210
  }
204
211
  if (bodiedSyncBlockAdded.length > 0) {
205
212
  if (Boolean(tr.getMeta(pmHistoryPluginKey))) {
@@ -25,7 +25,7 @@ var onDismissed = function onDismissed(syncBlockStore) {
25
25
  });
26
26
  };
27
27
  };
28
- export var handleBodiedSyncBlockRemoval = function handleBodiedSyncBlockRemoval(tr, bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef) {
28
+ export var handleBodiedSyncBlockRemoval = function handleBodiedSyncBlockRemoval(bodiedSyncBlockRemoved, syncBlockStore, api, confirmationTransactionRef, deletionReason) {
29
29
  // Clear potential old pending deletion to retreat the deletion as first attempt
30
30
  syncBlockStore.sourceManager.clearPendingDeletion();
31
31
 
@@ -35,7 +35,7 @@ export var handleBodiedSyncBlockRemoval = function handleBodiedSyncBlockRemoval(
35
35
  // proceed with deletion.
36
36
  syncBlockStore.sourceManager.deleteSyncBlocksWithConfirmation(bodiedSyncBlockRemoved.map(function (node) {
37
37
  return node.attrs;
38
- }), function () {
38
+ }), deletionReason, function () {
39
39
  var _api$core2;
40
40
  var confirmationTransaction = confirmationTransactionRef.current;
41
41
  if (!confirmationTransaction) {
@@ -0,0 +1,2 @@
1
+ ._195g1wug{margin-inline:auto}
2
+ ._1mou1wug{margin-block:auto}