@atlaskit/smart-card 20.0.4 → 20.1.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/cjs/state/actions/constants.js +4 -2
  3. package/dist/cjs/state/actions/index.js +51 -19
  4. package/dist/cjs/state/analytics/useSmartLinkAnalytics.js +26 -6
  5. package/dist/cjs/version.json +1 -1
  6. package/dist/cjs/view/BlockCard/index.js +29 -0
  7. package/dist/cjs/view/CardWithUrl/component.js +7 -2
  8. package/dist/cjs/view/CardWithUrl/loader.js +7 -9
  9. package/dist/cjs/view/EmbedCard/index.js +29 -0
  10. package/dist/cjs/view/FlexibleCard/components/blocks/element-group/index.js +2 -2
  11. package/dist/cjs/view/FlexibleCard/index.js +34 -7
  12. package/dist/cjs/view/HoverCard/components/HoverCardComponent.js +143 -0
  13. package/dist/cjs/view/HoverCard/components/{hover-card-content.js → HoverCardContent.js} +29 -25
  14. package/dist/cjs/view/HoverCard/index.js +25 -126
  15. package/dist/cjs/view/HoverCard/styled.js +11 -1
  16. package/dist/cjs/view/HoverCard/utils.js +3 -3
  17. package/dist/cjs/view/InlineCard/index.js +29 -0
  18. package/dist/es2019/state/actions/constants.js +2 -1
  19. package/dist/es2019/state/actions/index.js +46 -18
  20. package/dist/es2019/state/analytics/useSmartLinkAnalytics.js +24 -7
  21. package/dist/es2019/version.json +1 -1
  22. package/dist/es2019/view/BlockCard/index.js +29 -0
  23. package/dist/es2019/view/CardWithUrl/component.js +7 -2
  24. package/dist/es2019/view/CardWithUrl/loader.js +8 -10
  25. package/dist/es2019/view/EmbedCard/index.js +29 -0
  26. package/dist/es2019/view/FlexibleCard/components/blocks/element-group/index.js +4 -0
  27. package/dist/es2019/view/FlexibleCard/index.js +32 -7
  28. package/dist/es2019/view/HoverCard/components/HoverCardComponent.js +102 -0
  29. package/dist/es2019/view/HoverCard/components/{hover-card-content.js → HoverCardContent.js} +18 -21
  30. package/dist/es2019/view/HoverCard/index.js +26 -97
  31. package/dist/es2019/view/HoverCard/styled.js +8 -0
  32. package/dist/es2019/view/HoverCard/utils.js +1 -1
  33. package/dist/es2019/view/InlineCard/index.js +29 -0
  34. package/dist/esm/state/actions/constants.js +2 -1
  35. package/dist/esm/state/actions/index.js +51 -20
  36. package/dist/esm/state/analytics/useSmartLinkAnalytics.js +27 -7
  37. package/dist/esm/version.json +1 -1
  38. package/dist/esm/view/BlockCard/index.js +29 -0
  39. package/dist/esm/view/CardWithUrl/component.js +7 -2
  40. package/dist/esm/view/CardWithUrl/loader.js +8 -10
  41. package/dist/esm/view/EmbedCard/index.js +29 -0
  42. package/dist/esm/view/FlexibleCard/components/blocks/element-group/index.js +2 -2
  43. package/dist/esm/view/FlexibleCard/index.js +33 -7
  44. package/dist/esm/view/HoverCard/components/HoverCardComponent.js +114 -0
  45. package/dist/esm/view/HoverCard/components/{hover-card-content.js → HoverCardContent.js} +20 -22
  46. package/dist/esm/view/HoverCard/index.js +24 -109
  47. package/dist/esm/view/HoverCard/styled.js +8 -0
  48. package/dist/esm/view/HoverCard/utils.js +1 -1
  49. package/dist/esm/view/InlineCard/index.js +29 -0
  50. package/dist/types/state/actions/constants.d.ts +1 -0
  51. package/dist/types/state/actions/index.d.ts +1 -0
  52. package/dist/types/state/analytics/useSmartLinkAnalytics.d.ts +9 -0
  53. package/dist/types/state/hooks/useSmartLink.d.ts +3 -0
  54. package/dist/types/view/BlockCard/types.d.ts +2 -0
  55. package/dist/types/view/Card/index.d.ts +1 -1
  56. package/dist/types/view/Card/types.d.ts +2 -1
  57. package/dist/types/view/CardWithUrl/component.d.ts +1 -1
  58. package/dist/types/view/CardWithUrl/types.d.ts +2 -1
  59. package/dist/types/view/EmbedCard/components/styled.d.ts +16 -16
  60. package/dist/types/view/EmbedCard/types.d.ts +2 -0
  61. package/dist/types/view/FlexibleCard/types.d.ts +9 -4
  62. package/dist/types/view/HoverCard/components/HoverCardComponent.d.ts +4 -0
  63. package/dist/types/view/HoverCard/components/HoverCardContent.d.ts +9 -0
  64. package/dist/types/view/HoverCard/index.d.ts +2 -4
  65. package/dist/types/view/HoverCard/styled.d.ts +2 -0
  66. package/dist/types/view/HoverCard/types.d.ts +8 -0
  67. package/dist/types/view/HoverCard/utils.d.ts +1 -1
  68. package/dist/types/view/InlineCard/Frame/styled.d.ts +1 -1
  69. package/dist/types/view/InlineCard/Icon.d.ts +3 -3
  70. package/dist/types/view/InlineCard/IconAndTitleLayout/styled.d.ts +9 -9
  71. package/dist/types/view/InlineCard/ResolvingView/styled.d.ts +13 -5
  72. package/dist/types/view/InlineCard/styled.d.ts +8 -4
  73. package/dist/types/view/InlineCard/types.d.ts +2 -1
  74. package/dist/types/view/types.d.ts +6 -0
  75. package/package.json +4 -4
  76. package/dist/types/view/HoverCard/components/hover-card-content.d.ts +0 -4
@@ -1,10 +1,9 @@
1
1
  import { useMemo, useCallback } from 'react';
2
2
  import { auth } from '@atlaskit/outbound-auth-flow-client';
3
- import { APIError } from '@atlaskit/linking-common';
4
3
  import { useSmartLinkContext } from '@atlaskit/link-provider';
5
- import { ACTION_PENDING, ACTION_RESOLVED, ACTION_ERROR, ACTION_ERROR_FALLBACK, ACTION_RELOADING, cardAction } from '@atlaskit/linking-common';
4
+ import { ACTION_PENDING, ACTION_RESOLVED, ACTION_ERROR, ACTION_ERROR_FALLBACK, ACTION_RELOADING, ACTION_UPDATE_METADATA_STATUS, cardAction, APIError } from '@atlaskit/linking-common';
6
5
  import { getDefinitionId, getByDefinitionId, getServices, getStatus, getExtensionKey } from '../helpers';
7
- import { ERROR_MESSAGE_OAUTH, ERROR_MESSAGE_FATAL } from './constants';
6
+ import { ERROR_MESSAGE_OAUTH, ERROR_MESSAGE_FATAL, ERROR_MESSAGE_METADATA } from './constants';
8
7
  import * as measure from '../../utils/performance';
9
8
  import { getUnauthorizedJsonLd } from '../../utils/jsonld';
10
9
  import { addMetadataToExperience } from '../analytics';
@@ -20,14 +19,26 @@ export const useSmartCardActions = (id, url, analytics) => {
20
19
  } = store;
21
20
  const {
22
21
  details,
23
- status
22
+ status,
23
+ metadataStatus
24
24
  } = getState()[url] || {
25
25
  status: 'pending',
26
26
  details: undefined
27
27
  };
28
28
  const hasAuthFlowSupported = config.authFlow !== 'disabled';
29
29
  const hasData = !!(details && details.data);
30
- const handleResolvedLinkError = useCallback((url, error, response) => {
30
+ const setMetadataStatus = useCallback(metadataStatus => {
31
+ dispatch(cardAction(ACTION_UPDATE_METADATA_STATUS, {
32
+ url
33
+ }, undefined, undefined, metadataStatus));
34
+ }, [dispatch, url]);
35
+ const handleResolvedLinkError = useCallback((url, error, response, isMetadataRequest) => {
36
+ // If metadata request then set metadata status, return and do not alter link status
37
+ if (isMetadataRequest) {
38
+ setMetadataStatus('errored');
39
+ return;
40
+ }
41
+
31
42
  if (error.kind === 'fatal') {
32
43
  // If there's no previous data in the store for this URL, then bail
33
44
  // out and let the editor handle fallbacks (returns to a blue link).
@@ -64,24 +75,32 @@ export const useSmartCardActions = (id, url, analytics) => {
64
75
  }, undefined, error));
65
76
  }
66
77
  }
67
- }, [hasData, status, dispatch, details]);
68
- const handleResolvedLinkResponse = useCallback((resourceUrl, response, isReloading = false) => {
78
+ }, [setMetadataStatus, hasData, status, dispatch, details]);
79
+ const handleResolvedLinkResponse = useCallback((resourceUrl, response, isReloading = false, isMetadataRequest) => {
69
80
  const hostname = new URL(resourceUrl).hostname;
70
81
  const nextStatus = response ? getStatus(response) : 'fatal'; // If we require authorization & do not have an authFlow available,
71
82
  // throw an error and render as a normal blue link.
72
83
 
73
84
  if ((nextStatus === 'unauthorized' || nextStatus === 'forbidden') && !hasAuthFlowSupported) {
74
- handleResolvedLinkError(resourceUrl, new APIError('fallback', hostname, ERROR_MESSAGE_OAUTH), response);
85
+ handleResolvedLinkError(resourceUrl, new APIError('fallback', hostname, ERROR_MESSAGE_OAUTH), response, isMetadataRequest);
75
86
  return;
76
87
  } // Handle any other errors
77
88
 
78
89
 
79
90
  if (nextStatus === 'fatal') {
80
- handleResolvedLinkError(resourceUrl, new APIError('fatal', hostname, ERROR_MESSAGE_FATAL));
91
+ handleResolvedLinkError(resourceUrl, new APIError('fatal', hostname, ERROR_MESSAGE_FATAL), undefined, isMetadataRequest);
81
92
  return;
82
- } // Dispatch Analytics and resolved card action - including unauthorized states.
93
+ } // Handle errors of any other kind in the metadata request response
83
94
 
84
95
 
96
+ if (isMetadataRequest && nextStatus !== 'resolved') {
97
+ handleResolvedLinkError(resourceUrl, new APIError('error', hostname, ERROR_MESSAGE_METADATA), response, isMetadataRequest);
98
+ return;
99
+ } //if a link resolves normally, metadata will also always be resolved
100
+
101
+
102
+ setMetadataStatus('resolved'); // Dispatch Analytics and resolved card action - including unauthorized states.
103
+
85
104
  if (isReloading) {
86
105
  dispatch(cardAction(ACTION_RELOADING, {
87
106
  url: resourceUrl
@@ -89,15 +108,15 @@ export const useSmartCardActions = (id, url, analytics) => {
89
108
  } else {
90
109
  dispatch(cardAction(ACTION_RESOLVED, {
91
110
  url: resourceUrl
92
- }, response));
111
+ }, response, undefined, undefined, isMetadataRequest));
93
112
  }
94
- }, [dispatch, handleResolvedLinkError, hasAuthFlowSupported]);
95
- const resolve = useCallback(async (resourceUrl = url, isReloading = false) => {
113
+ }, [dispatch, handleResolvedLinkError, hasAuthFlowSupported, setMetadataStatus]);
114
+ const resolve = useCallback(async (resourceUrl = url, isReloading = false, isMetadataRequest = false) => {
96
115
  // Request JSON-LD data for the card from ORS, iff it has extended
97
116
  // its cache lifespan OR there is no data for it currently. Once the data
98
117
  // has come back asynchronously, dispatch the resolved action for the card.
99
- if (isReloading || !hasData) {
100
- return connections.client.fetchData(resourceUrl).then(response => handleResolvedLinkResponse(resourceUrl, response, isReloading)).catch(error => handleResolvedLinkError(resourceUrl, error));
118
+ if (isReloading || !hasData || isMetadataRequest) {
119
+ return connections.client.fetchData(resourceUrl).then(response => handleResolvedLinkResponse(resourceUrl, response, isReloading, isMetadataRequest)).catch(error => handleResolvedLinkError(resourceUrl, error, undefined, isMetadataRequest));
101
120
  } else {
102
121
  addMetadataToExperience('smart-link-rendered', id, {
103
122
  cached: true
@@ -109,10 +128,11 @@ export const useSmartCardActions = (id, url, analytics) => {
109
128
  dispatch(cardAction(ACTION_PENDING, {
110
129
  url
111
130
  }));
131
+ setMetadataStatus('pending');
112
132
  }
113
133
 
114
134
  return resolve();
115
- }, [url, resolve, details, dispatch]);
135
+ }, [details, resolve, dispatch, url, setMetadataStatus]);
116
136
  const reload = useCallback(() => {
117
137
  const definitionId = getDefinitionId(details);
118
138
 
@@ -122,6 +142,13 @@ export const useSmartCardActions = (id, url, analytics) => {
122
142
  resolve(url, true);
123
143
  }
124
144
  }, [url, details, getState, resolve]);
145
+ const loadMetadata = useCallback(() => {
146
+ //metadataStatus will be undefined for SSR links only
147
+ if (metadataStatus === undefined) {
148
+ setMetadataStatus('pending');
149
+ return resolve(url, false, true);
150
+ }
151
+ }, [metadataStatus, resolve, setMetadataStatus, url]);
125
152
  const authorize = useCallback(appearance => {
126
153
  const definitionId = getDefinitionId(details);
127
154
  const extensionKey = getExtensionKey(details);
@@ -187,6 +214,7 @@ export const useSmartCardActions = (id, url, analytics) => {
187
214
  register,
188
215
  reload,
189
216
  authorize,
190
- invoke
191
- }), [register, reload, authorize, invoke]);
217
+ invoke,
218
+ loadMetadata
219
+ }), [register, reload, authorize, invoke, loadMetadata]);
192
220
  };
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  import { getUrl } from '@atlaskit/linking-common';
3
- import { uiAuthEvent, uiAuthAlternateAccountEvent, uiCardClickedEvent, uiActionClickedEvent, uiClosedAuthEvent, uiRenderSuccessEvent, uiHoverCardViewedEvent, uiHoverCardDismissedEvent, uiHoverCardOpenLinkClickedEvent, invokeSucceededEvent, invokeFailedEvent, connectSucceededEvent, connectFailedEvent, trackAppAccountConnected, screenAuthPopupEvent, instrumentEvent } from '../../utils/analytics';
3
+ import { uiAuthEvent, uiAuthAlternateAccountEvent, uiCardClickedEvent, uiActionClickedEvent, uiClosedAuthEvent, uiRenderSuccessEvent, uiRenderFailedEvent, uiHoverCardViewedEvent, uiHoverCardDismissedEvent, uiHoverCardOpenLinkClickedEvent, invokeSucceededEvent, invokeFailedEvent, connectSucceededEvent, connectFailedEvent, trackAppAccountConnected, screenAuthPopupEvent, instrumentEvent } from '../../utils/analytics';
4
4
  import { failUfoExperience, startUfoExperience, succeedUfoExperience } from './ufoExperiences';
5
5
  import { getDefinitionId, getExtensionKey, getResourceType, getSubproduct, getProduct } from '../helpers';
6
6
  import { useSmartLinkContext } from '@atlaskit/link-provider';
@@ -20,12 +20,13 @@ export const useSmartLinkAnalytics = (url, dispatchAnalytics, id, defaultLocatio
20
20
  const {
21
21
  store
22
22
  } = useSmartLinkContext();
23
- const state = getUrl(store, url);
24
- const extractedDefinitionId = getDefinitionId(state.details);
25
- const extractedExtensionKey = getExtensionKey(state.details);
26
- const extractedResourceType = getResourceType(state.details);
27
- const extractedSubproduct = getSubproduct(state.details);
28
- const extractedProduct = getProduct(state.details);
23
+ const state = store ? getUrl(store, url) : undefined;
24
+ const details = state ? state.details : undefined;
25
+ const extractedDefinitionId = getDefinitionId(details);
26
+ const extractedExtensionKey = getExtensionKey(details);
27
+ const extractedResourceType = getResourceType(details);
28
+ const extractedSubproduct = getSubproduct(details);
29
+ const extractedProduct = getProduct(details);
29
30
  /** Contains all ui analytics events */
30
31
 
31
32
  const ui = useMemo(() => ({
@@ -124,6 +125,22 @@ export const useSmartLinkAnalytics = (url, dispatchAnalytics, id, defaultLocatio
124
125
  dispatchAnalytics(uiRenderSuccessEvent(display, status, definitionId, extensionKey));
125
126
  },
126
127
 
128
+ /**
129
+ * This fires an event that represents when a Smart Link renders unsuccessfuly.
130
+ * @param display Whether the card was an Inline, Block, Embed or Flexible UI.
131
+ * @param id The unique ID for this Smart Link.
132
+ * @param error: An error representing why the Smart Link render failed.
133
+ * @param errorInfo: Additional details about the error including the stack trace.
134
+ */
135
+ renderFailedEvent: (display, id = defaultId, error, errorInfo) => {
136
+ // Start and fail the smart-link-rendered experience. If it has already
137
+ // been started nothing happens.
138
+ startUfoExperience('smart-link-rendered', id);
139
+ failUfoExperience('smart-link-rendered', id);
140
+ failUfoExperience('smart-link-authenticated', id);
141
+ dispatchAnalytics(uiRenderFailedEvent(display, error, errorInfo));
142
+ },
143
+
127
144
  /**
128
145
  * This fires an event that represents a hover preview being opened.
129
146
  * @param hoverDisplay Whether the hover preview was displayed as a card or embed.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-card",
3
- "version": "20.0.4",
3
+ "version": "20.1.0",
4
4
  "sideEffects": false
5
5
  }
@@ -30,6 +30,7 @@ export const BlockCard = ({
30
30
  renderers,
31
31
  isSelected,
32
32
  onResolve,
33
+ onError,
33
34
  testId,
34
35
  showActions,
35
36
  platform
@@ -70,6 +71,13 @@ export const BlockCard = ({
70
71
  }));
71
72
 
72
73
  case 'unauthorized':
74
+ if (onError) {
75
+ onError({
76
+ url,
77
+ status
78
+ });
79
+ }
80
+
73
81
  const unauthorizedViewProps = extractBlockProps(data, meta, extractorOpts);
74
82
  return /*#__PURE__*/React.createElement(BlockCardUnauthorisedView, _extends({}, unauthorizedViewProps, {
75
83
  isSelected: isSelected,
@@ -80,6 +88,13 @@ export const BlockCard = ({
80
88
  }));
81
89
 
82
90
  case 'forbidden':
91
+ if (onError) {
92
+ onError({
93
+ url,
94
+ status
95
+ });
96
+ }
97
+
83
98
  const forbiddenViewProps = extractBlockProps(data, meta, extractorOpts);
84
99
  const cardMetadata = (_details$meta = details === null || details === void 0 ? void 0 : details.meta) !== null && _details$meta !== void 0 ? _details$meta : getUnauthorizedJsonLd().meta;
85
100
  const requestAccessContext = extractRequestAccessContext({
@@ -96,6 +111,13 @@ export const BlockCard = ({
96
111
  }));
97
112
 
98
113
  case 'not_found':
114
+ if (onError) {
115
+ onError({
116
+ url,
117
+ status
118
+ });
119
+ }
120
+
99
121
  const notFoundViewProps = extractBlockProps(data, meta, extractorOpts);
100
122
  return /*#__PURE__*/React.createElement(BlockCardNotFoundView, _extends({}, notFoundViewProps, {
101
123
  isSelected: isSelected,
@@ -105,6 +127,13 @@ export const BlockCard = ({
105
127
 
106
128
  case 'fallback':
107
129
  case 'errored':
130
+ if (onError) {
131
+ onError({
132
+ url,
133
+ status
134
+ });
135
+ }
136
+
108
137
  if (authFlow && authFlow === 'disabled') {
109
138
  return /*#__PURE__*/React.createElement(CardLinkView, {
110
139
  link: url,
@@ -20,6 +20,7 @@ export function CardWithUrlContent({
20
20
  appearance,
21
21
  dispatchAnalytics,
22
22
  onResolve,
23
+ onError,
23
24
  testId,
24
25
  showActions,
25
26
  inheritDimensions,
@@ -91,7 +92,7 @@ export function CardWithUrlContent({
91
92
  if (isFinalState(state.status)) {
92
93
  analytics.ui.renderSuccessEvent(appearance, state.status, id, definitionId, extensionKey);
93
94
  }
94
- }, [appearance, state.details, state.status, url, definitionId, extensionKey, analytics.ui, id]);
95
+ }, [appearance, state.status, url, definitionId, extensionKey, analytics.ui, id]);
95
96
 
96
97
  if (isFlexibleUi) {
97
98
  let cardState = state;
@@ -115,7 +116,8 @@ export function CardWithUrlContent({
115
116
  ui: ui,
116
117
  url: url,
117
118
  testId: testId,
118
- onResolve: onResolve
119
+ onResolve: onResolve,
120
+ onError: onError
119
121
  }, children);
120
122
  } // We have to keep this last to prevent hook order from being violated
121
123
 
@@ -135,6 +137,7 @@ export function CardWithUrlContent({
135
137
  handleFrameClick: handleClickWrapper,
136
138
  isSelected: isSelected,
137
139
  onResolve: onResolve,
140
+ onError: onError,
138
141
  testId: testId,
139
142
  inlinePreloaderStyle: inlinePreloaderStyle,
140
143
  showHoverPreview: showHoverPreview
@@ -154,6 +157,7 @@ export function CardWithUrlContent({
154
157
  handleAnalytics: dispatchAnalytics,
155
158
  isSelected: isSelected,
156
159
  onResolve: onResolve,
160
+ onError: onError,
157
161
  testId: testId,
158
162
  showActions: showActions,
159
163
  platform: platform
@@ -173,6 +177,7 @@ export function CardWithUrlContent({
173
177
  isFrameVisible: isFrameVisible,
174
178
  platform: platform,
175
179
  onResolve: onResolve,
180
+ onError: onError,
176
181
  testId: testId,
177
182
  inheritDimensions: inheritDimensions,
178
183
  showActions: showActions,
@@ -1,9 +1,9 @@
1
1
  import React, { lazy, useEffect, useState, Suspense, useCallback } from 'react';
2
2
  import uuid from 'uuid';
3
- import { uiRenderFailedEvent, fireSmartLinkEvent } from '../../utils/analytics';
3
+ import { fireSmartLinkEvent } from '../../utils/analytics';
4
4
  import { clearMarks, clearMeasures } from '../../utils/performance';
5
5
  import { ErrorBoundary } from 'react-error-boundary';
6
- import { failUfoExperience, startUfoExperience } from '../../state/analytics/ufoExperiences';
6
+ import { useSmartLinkAnalytics } from '../../state/analytics';
7
7
  import { LoadingCardLink } from './component-lazy/LazyFallback';
8
8
  const LazyCardWithUrlContent = /*#__PURE__*/lazy(() => import(
9
9
  /* webpackChunkName: "@atlaskit-internal_smartcard-urlcardcontent" */
@@ -25,6 +25,7 @@ export function CardWithURLRenderer(props) {
25
25
  onClick,
26
26
  container,
27
27
  onResolve,
28
+ onError,
28
29
  testId,
29
30
  showActions,
30
31
  inheritDimensions,
@@ -50,6 +51,7 @@ export function CardWithURLRenderer(props) {
50
51
 
51
52
  fireSmartLinkEvent(analyticsPayload, createAnalyticsEvent);
52
53
  }, [appearance, createAnalyticsEvent]);
54
+ const analytics = useSmartLinkAnalytics(url !== null && url !== void 0 ? url : '', dispatchAnalytics, id);
53
55
  const logError = useCallback((error, info) => {
54
56
  const {
55
57
  componentStack
@@ -65,15 +67,10 @@ export function CardWithURLRenderer(props) {
65
67
  else {
66
68
  const errorInfo = {
67
69
  componentStack
68
- }; // Start and fail the smart-link-rendered experience. If it has already
69
- // been started nothing happens.
70
-
71
- startUfoExperience('smart-link-rendered', id);
72
- failUfoExperience('smart-link-rendered', id);
73
- failUfoExperience('smart-link-authenticated', id);
74
- dispatchAnalytics(uiRenderFailedEvent(appearance, error, errorInfo));
70
+ };
71
+ analytics.ui.renderFailedEvent(appearance, id, error, errorInfo);
75
72
  }
76
- }, [appearance, dispatchAnalytics, id]);
73
+ }, [analytics.ui, appearance, id]);
77
74
 
78
75
  if (!url) {
79
76
  throw new Error('@atlaskit/smart-card: url property is missing.');
@@ -89,6 +86,7 @@ export function CardWithURLRenderer(props) {
89
86
  dispatchAnalytics,
90
87
  container,
91
88
  onResolve,
89
+ onError,
92
90
  testId,
93
91
  showActions,
94
92
  inheritDimensions,
@@ -29,6 +29,7 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
29
29
  isFrameVisible,
30
30
  platform,
31
31
  onResolve,
32
+ onError,
32
33
  testId,
33
34
  inheritDimensions
34
35
  }, iframeRef) => {
@@ -91,6 +92,13 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
91
92
  }
92
93
 
93
94
  case 'unauthorized':
95
+ if (onError) {
96
+ onError({
97
+ url,
98
+ status
99
+ });
100
+ }
101
+
94
102
  const unauthorisedViewProps = extractEmbedProps(data, meta, platform);
95
103
  return /*#__PURE__*/React.createElement(EmbedCardUnauthorisedView, _extends({}, unauthorisedViewProps, {
96
104
  isSelected: isSelected,
@@ -100,6 +108,13 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
100
108
  }));
101
109
 
102
110
  case 'forbidden':
111
+ if (onError) {
112
+ onError({
113
+ url,
114
+ status
115
+ });
116
+ }
117
+
103
118
  const forbiddenViewProps = extractEmbedProps(data, meta, platform);
104
119
  const cardMetadata = (_details$meta = details === null || details === void 0 ? void 0 : details.meta) !== null && _details$meta !== void 0 ? _details$meta : getUnauthorizedJsonLd().meta;
105
120
  const requestAccessContext = extractRequestAccessContext({
@@ -116,6 +131,13 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
116
131
  }));
117
132
 
118
133
  case 'not_found':
134
+ if (onError) {
135
+ onError({
136
+ url,
137
+ status
138
+ });
139
+ }
140
+
119
141
  const notFoundViewProps = extractEmbedProps(data, meta, platform);
120
142
  return /*#__PURE__*/React.createElement(EmbedCardNotFoundView, _extends({}, notFoundViewProps, {
121
143
  isSelected: isSelected,
@@ -125,6 +147,13 @@ export const EmbedCard = /*#__PURE__*/React.forwardRef(({
125
147
 
126
148
  case 'fallback':
127
149
  case 'errored':
150
+ if (onError) {
151
+ onError({
152
+ url,
153
+ status
154
+ });
155
+ }
156
+
128
157
  return /*#__PURE__*/React.createElement(EmbedCardErroredView, {
129
158
  onRetry: handleErrorRetry,
130
159
  inheritDimensions: inheritDimensions,
@@ -9,6 +9,8 @@ const getAlignmentStyles = align => {
9
9
  switch (align) {
10
10
  case SmartLinkAlignment.Right:
11
11
  return css`
12
+ -webkit-box-align: end;
13
+ -ms-flex-align: end;
12
14
  justify-content: flex-end;
13
15
  text-align: right;
14
16
  `;
@@ -16,6 +18,8 @@ const getAlignmentStyles = align => {
16
18
  case SmartLinkAlignment.Left:
17
19
  default:
18
20
  return css`
21
+ -webkit-box-align: start;
22
+ -ms-flex-align: start;
19
23
  justify-content: flex-start;
20
24
  text-align: left;
21
25
  `;
@@ -1,5 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import React, { useEffect } from 'react';
3
+ import { SmartLinkStatus } from '../../constants';
3
4
  import Container from './components/container';
4
5
  import { FlexibleUiContext } from '../../state/flexible-ui-context';
5
6
  import { getContextByStatus, getRetryOptions } from './utils';
@@ -14,12 +15,13 @@ const FlexibleCard = ({
14
15
  cardState,
15
16
  children,
16
17
  onAuthorize,
18
+ onError,
19
+ onResolve,
17
20
  renderers,
18
21
  ui,
19
22
  url,
20
23
  onClick,
21
- testId,
22
- onResolve
24
+ testId
23
25
  }) => {
24
26
  const {
25
27
  status: cardType,
@@ -28,13 +30,36 @@ const FlexibleCard = ({
28
30
  const status = cardType;
29
31
  const context = getContextByStatus(url, status, details, renderers);
30
32
  const retry = getRetryOptions(url, status, details, onAuthorize);
33
+ const {
34
+ title
35
+ } = context || {};
31
36
  useEffect(() => {
32
- if (status === 'resolved' && onResolve) {
33
- onResolve({
34
- url
35
- });
37
+ switch (status) {
38
+ case SmartLinkStatus.Resolved:
39
+ if (onResolve) {
40
+ onResolve({
41
+ title,
42
+ url
43
+ });
44
+ }
45
+
46
+ break;
47
+
48
+ case SmartLinkStatus.Errored:
49
+ case SmartLinkStatus.Fallback:
50
+ case SmartLinkStatus.Forbidden:
51
+ case SmartLinkStatus.NotFound:
52
+ case SmartLinkStatus.Unauthorized:
53
+ if (onError) {
54
+ onError({
55
+ status,
56
+ url
57
+ });
58
+ }
59
+
60
+ break;
36
61
  }
37
- }, [onResolve, status, url]);
62
+ }, [onError, onResolve, status, title, url]);
38
63
  return /*#__PURE__*/React.createElement(FlexibleUiContext.Provider, {
39
64
  value: context
40
65
  }, /*#__PURE__*/React.createElement(Container, _extends({
@@ -0,0 +1,102 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+
3
+ /** @jsx jsx */
4
+ import Popup from '@atlaskit/popup';
5
+ import { jsx } from '@emotion/core';
6
+ import React, { useCallback, useRef } from 'react';
7
+ import { useSmartLinkActions } from '../../../state/hooks-external/useSmartLinkActions';
8
+ import { useSmartLinkRenderers } from '../../../state/renderers';
9
+ import { useSmartCardState as useLinkState } from '../../../state/store';
10
+ import HoverCardContent from '../components/HoverCardContent';
11
+ import { HoverCardContainer } from '../styled';
12
+ import { SMART_CARD_ANALYTICS_DISPLAY } from '../utils';
13
+ export const hoverCardClassName = 'smart-links-hover-preview';
14
+ export const HoverCardComponent = ({
15
+ children,
16
+ url,
17
+ analyticsHandler,
18
+ analytics
19
+ }) => {
20
+ const delay = 300;
21
+ const [isOpen, setIsOpen] = React.useState(false);
22
+ const fadeOutTimeoutId = useRef();
23
+ const fadeInTimeoutId = useRef();
24
+ const cardOpenTime = useRef();
25
+ const renderers = useSmartLinkRenderers();
26
+ const linkState = useLinkState(url);
27
+ const hoverDisplay = 'card';
28
+ const invokeMethod = 'mouse_hover';
29
+ const hideCard = useCallback(() => {
30
+ //Check its previously open to avoid firing events when moving between the child and hover card components
31
+ if (isOpen === true && cardOpenTime.current) {
32
+ const hoverTime = Date.now() - cardOpenTime.current;
33
+ analytics.ui.hoverCardDismissedEvent('card', hoverTime, 'mouse_hover');
34
+ }
35
+
36
+ setIsOpen(false);
37
+ }, [analytics.ui, isOpen]);
38
+ const initHideCard = useCallback(() => {
39
+ if (fadeInTimeoutId.current) {
40
+ clearTimeout(fadeInTimeoutId.current);
41
+ }
42
+
43
+ fadeOutTimeoutId.current = setTimeout(() => hideCard(), delay);
44
+ }, [hideCard]);
45
+ const initShowCard = useCallback(() => {
46
+ if (fadeOutTimeoutId.current) {
47
+ clearTimeout(fadeOutTimeoutId.current);
48
+ }
49
+
50
+ fadeInTimeoutId.current = setTimeout(() => {
51
+ //Check if its previously closed to avoid firing events when moving between the child and hover card components
52
+ if (isOpen === false) {
53
+ cardOpenTime.current = Date.now();
54
+ analytics.ui.hoverCardViewedEvent(hoverDisplay, invokeMethod);
55
+ }
56
+
57
+ setIsOpen(true);
58
+ }, delay);
59
+ }, [delay, isOpen, analytics.ui]);
60
+ const linkActions = useSmartLinkActions({
61
+ url,
62
+ appearance: SMART_CARD_ANALYTICS_DISPLAY,
63
+ analyticsHandler
64
+ });
65
+ const onActionClick = useCallback(actionId => {
66
+ if (actionId === 'preview-content') {
67
+ hideCard();
68
+ }
69
+ }, [hideCard]); // Stop hover preview content to propagate event to parent.
70
+
71
+ const onClick = useCallback(e => e.stopPropagation(), []);
72
+ return jsx(Popup, {
73
+ testId: "hover-card",
74
+ isOpen: isOpen,
75
+ onClose: hideCard,
76
+ placement: "bottom-start",
77
+ content: ({
78
+ update
79
+ }) => jsx("div", {
80
+ onMouseEnter: initShowCard,
81
+ onMouseLeave: initHideCard,
82
+ onClick: onClick,
83
+ css: HoverCardContainer // class name for trello to exclude click events
84
+ ,
85
+ className: hoverCardClassName
86
+ }, jsx(HoverCardContent, {
87
+ analytics: analytics,
88
+ cardActions: linkActions,
89
+ cardState: linkState,
90
+ onActionClick: onActionClick,
91
+ onResolve: update,
92
+ renderers: renderers,
93
+ url: url
94
+ })),
95
+ trigger: triggerProps => jsx("span", _extends({}, triggerProps, {
96
+ onMouseEnter: initShowCard,
97
+ onMouseLeave: initHideCard
98
+ }), children),
99
+ zIndex: 511 // Temporary fix for Confluence inline comment on editor mod has z-index of 500, Jira issue view has z-index of 510
100
+
101
+ });
102
+ };