@atlaskit/smart-card 23.12.2 → 23.13.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 (36) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/linkUrl.js +15 -0
  3. package/dist/cjs/messages.js +20 -0
  4. package/dist/cjs/version.json +1 -1
  5. package/dist/cjs/view/BlockCard/views/flexible/FlexibleResolvedView.js +18 -3
  6. package/dist/cjs/view/FlexibleCard/components/actions/action/index.js +1 -1
  7. package/dist/cjs/view/LinkUrl/LinkWarningModal/hooks/use-link-warning-modal.js +76 -0
  8. package/dist/cjs/view/LinkUrl/LinkWarningModal/index.js +70 -0
  9. package/dist/cjs/view/LinkUrl/index.js +48 -0
  10. package/dist/cjs/view/LinkUrl/types.js +5 -0
  11. package/dist/es2019/linkUrl.js +1 -0
  12. package/dist/es2019/messages.js +20 -0
  13. package/dist/es2019/version.json +1 -1
  14. package/dist/es2019/view/BlockCard/views/flexible/FlexibleResolvedView.js +18 -3
  15. package/dist/es2019/view/FlexibleCard/components/actions/action/index.js +1 -1
  16. package/dist/es2019/view/LinkUrl/LinkWarningModal/hooks/use-link-warning-modal.js +49 -0
  17. package/dist/es2019/view/LinkUrl/LinkWarningModal/index.js +46 -0
  18. package/dist/es2019/view/LinkUrl/index.js +28 -0
  19. package/dist/es2019/view/LinkUrl/types.js +1 -0
  20. package/dist/esm/linkUrl.js +1 -0
  21. package/dist/esm/messages.js +20 -0
  22. package/dist/esm/version.json +1 -1
  23. package/dist/esm/view/BlockCard/views/flexible/FlexibleResolvedView.js +18 -3
  24. package/dist/esm/view/FlexibleCard/components/actions/action/index.js +1 -1
  25. package/dist/esm/view/LinkUrl/LinkWarningModal/hooks/use-link-warning-modal.js +62 -0
  26. package/dist/esm/view/LinkUrl/LinkWarningModal/index.js +46 -0
  27. package/dist/esm/view/LinkUrl/index.js +33 -0
  28. package/dist/esm/view/LinkUrl/types.js +1 -0
  29. package/dist/types/linkUrl.d.ts +2 -0
  30. package/dist/types/messages.d.ts +1 -1
  31. package/dist/types/view/LinkUrl/LinkWarningModal/hooks/use-link-warning-modal.d.ts +9 -0
  32. package/dist/types/view/LinkUrl/LinkWarningModal/index.d.ts +13 -0
  33. package/dist/types/view/LinkUrl/index.d.ts +4 -0
  34. package/dist/types/view/LinkUrl/types.d.ts +8 -0
  35. package/link-url/package.json +15 -0
  36. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/smart-card
2
2
 
3
+ ## 23.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`67cfd8e13d0`](https://bitbucket.org/atlassian/atlassian-frontend/commits/67cfd8e13d0) - LinkUrl component with built-in link safety check added
8
+
9
+ ## 23.12.3
10
+
11
+ ### Patch Changes
12
+
13
+ - [`27b8d8e0b4b`](https://bitbucket.org/atlassian/atlassian-frontend/commits/27b8d8e0b4b) - [ux] This adds tooltip messages to custom Flexible UI actions.
14
+ Now, when hovering over custom actions, the "content" prop will be displayed in the tooltip.
15
+ This also affects hover preview actions, which now show the tooltip message when hovering over actions inside of the hover preview card.
16
+
17
+ Additionally, some changes have been made to Flexible UI block card actions so that the analytics fired have the same attribute action type as hover preview's.
18
+
3
19
  ## 23.12.2
4
20
 
5
21
  ### Patch Changes
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ Object.defineProperty(exports, "default", {
9
+ enumerable: true,
10
+ get: function get() {
11
+ return _LinkUrl.default;
12
+ }
13
+ });
14
+
15
+ var _LinkUrl = _interopRequireDefault(require("./view/LinkUrl"));
@@ -48,6 +48,11 @@ var messages = (0, _reactIntlNext.defineMessages)({
48
48
  defaultMessage: 'Close',
49
49
  description: ''
50
50
  },
51
+ check_this_link: {
52
+ id: 'fabric.linking.check_this_link',
53
+ defaultMessage: 'Check this link',
54
+ description: 'Link safety warning modal header'
55
+ },
51
56
  connect_to: {
52
57
  id: 'fabric.linking.connect_to',
53
58
  defaultMessage: 'Connect to {name}',
@@ -93,6 +98,11 @@ var messages = (0, _reactIntlNext.defineMessages)({
93
98
  defaultMessage: 'Connect to Atlassian to view more details of your work and collaborate from one place.',
94
99
  description: 'Shown when a user does not have access to a link, but can connect their external account to view the link on card view and we do not have the providers name.'
95
100
  },
101
+ continue: {
102
+ id: 'fabric.linking.continue',
103
+ defaultMessage: 'Continue',
104
+ description: 'continue'
105
+ },
96
106
  copy_url_to_clipboard: {
97
107
  id: 'fabric.linking.download',
98
108
  defaultMessage: 'Copy link',
@@ -138,6 +148,11 @@ var messages = (0, _reactIntlNext.defineMessages)({
138
148
  defaultMessage: 'You don’t have access to this preview. Contact the site admin if you need access.',
139
149
  description: 'Informs the user that they cannot view this content.'
140
150
  },
151
+ go_back: {
152
+ id: 'fabric.linking.go_back',
153
+ defaultMessage: 'Go back',
154
+ description: 'go back'
155
+ },
141
156
  invalid_permissions: {
142
157
  id: 'fabric.linking.invalid_permissions',
143
158
  defaultMessage: 'Restricted content',
@@ -343,6 +358,11 @@ var messages = (0, _reactIntlNext.defineMessages)({
343
358
  defaultMessage: 'Try another account',
344
359
  description: 'Allows the user to try an action again with a different account'
345
360
  },
361
+ link_safety_warning_message: {
362
+ id: 'fabric.linking.link_safety_warning_message',
363
+ defaultMessage: 'The link {unsafeLinkText} is taking you to a different site, <a>actual link here</a>',
364
+ description: 'Link safety check warning message'
365
+ },
346
366
  unlink_account: {
347
367
  id: 'fabric.linking.unlink_account',
348
368
  defaultMessage: 'Unlink Account',
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-card",
3
- "version": "23.12.2",
3
+ "version": "23.13.0",
4
4
  "sideEffects": false
5
5
  }
@@ -111,9 +111,14 @@ var FlexibleResolvedView = function FlexibleResolvedView(_ref) {
111
111
  name: _constants.ActionName.PreviewAction,
112
112
  hideIcon: true,
113
113
  onClick: function onClick() {
114
+ analytics.ui.actionClickedEvent({
115
+ id: analyticsId,
116
+ actionType: 'PreviewAction',
117
+ display: _constants.CardDisplay.Block
118
+ });
114
119
  analytics.operational.invokeSucceededEvent({
115
120
  id: analyticsId,
116
- actionType: 'preview',
121
+ actionType: 'PreviewAction',
117
122
  display: _constants.CardDisplay.Block
118
123
  });
119
124
  }
@@ -121,9 +126,14 @@ var FlexibleResolvedView = function FlexibleResolvedView(_ref) {
121
126
  name: _constants.ActionName.ViewAction,
122
127
  hideIcon: true,
123
128
  onClick: function onClick() {
129
+ analytics.ui.actionClickedEvent({
130
+ id: analyticsId,
131
+ actionType: 'ViewAction',
132
+ display: _constants.CardDisplay.Block
133
+ });
124
134
  analytics.operational.invokeSucceededEvent({
125
135
  id: analyticsId,
126
- actionType: 'view',
136
+ actionType: 'ViewAction',
127
137
  display: _constants.CardDisplay.Block
128
138
  });
129
139
  }
@@ -131,9 +141,14 @@ var FlexibleResolvedView = function FlexibleResolvedView(_ref) {
131
141
  name: _constants.ActionName.DownloadAction,
132
142
  hideIcon: true,
133
143
  onClick: function onClick() {
144
+ analytics.ui.actionClickedEvent({
145
+ id: analyticsId,
146
+ actionType: 'DownloadAction',
147
+ display: _constants.CardDisplay.Block
148
+ });
134
149
  analytics.operational.invokeSucceededEvent({
135
150
  id: analyticsId,
136
- actionType: 'download',
151
+ actionType: 'DownloadAction',
137
152
  display: _constants.CardDisplay.Block
138
153
  });
139
154
  }
@@ -75,7 +75,7 @@ var Action = function Action(_ref) {
75
75
  overrideCss: overrideCss,
76
76
  size: size,
77
77
  testId: testId,
78
- tooltipMessage: tooltipMessage
78
+ tooltipMessage: tooltipMessage || content
79
79
  });
80
80
  }
81
81
  };
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.useLinkWarningModal = void 0;
9
+
10
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
+
12
+ var _react = require("react");
13
+
14
+ var _url = require("@atlaskit/linking-common/url");
15
+
16
+ var useLinkWarningModal = function useLinkWarningModal() {
17
+ var _useState = (0, _react.useState)(null),
18
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
19
+ unsafeLinkText = _useState2[0],
20
+ setUnsafeLinkText = _useState2[1];
21
+
22
+ var _useState3 = (0, _react.useState)(null),
23
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
24
+ url = _useState4[0],
25
+ setUrl = _useState4[1];
26
+
27
+ var _useState5 = (0, _react.useState)(false),
28
+ _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
29
+ isOpen = _useState6[0],
30
+ setIsOpen = _useState6[1];
31
+ /**
32
+ * It checks and warns if a link text is a URL and different from an actual link destination
33
+ */
34
+
35
+
36
+ var checkLinkSafety = function checkLinkSafety(event, href) {
37
+ var linkText = event.currentTarget.innerText;
38
+ var anchorLinkRegex = new RegExp(/^#/im);
39
+ var isAnchorLink = anchorLinkRegex.test(linkText);
40
+
41
+ if (isAnchorLink) {
42
+ return;
43
+ }
44
+
45
+ var normalisedUrlFromLinkText = (0, _url.normalizeUrl)(linkText);
46
+
47
+ if (!!normalisedUrlFromLinkText && normalisedUrlFromLinkText !== href) {
48
+ event.preventDefault();
49
+ setUnsafeLinkText(linkText);
50
+ href && setUrl(href);
51
+ setIsOpen(true);
52
+ }
53
+ };
54
+
55
+ var onClose = function onClose() {
56
+ setIsOpen(false);
57
+ setUnsafeLinkText(null);
58
+ setUrl(null);
59
+ };
60
+
61
+ var onContinue = function onContinue() {
62
+ onClose();
63
+ url && window.location.assign(url);
64
+ };
65
+
66
+ return {
67
+ checkLinkSafety: checkLinkSafety,
68
+ unsafeLinkText: unsafeLinkText,
69
+ onClose: onClose,
70
+ onContinue: onContinue,
71
+ isOpen: isOpen,
72
+ url: url
73
+ };
74
+ };
75
+
76
+ exports.useLinkWarningModal = useLinkWarningModal;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ var _typeof = require("@babel/runtime/helpers/typeof");
6
+
7
+ Object.defineProperty(exports, "__esModule", {
8
+ value: true
9
+ });
10
+ exports.default = void 0;
11
+
12
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
13
+
14
+ var _react = _interopRequireDefault(require("react"));
15
+
16
+ var _standardButton = _interopRequireDefault(require("@atlaskit/button/standard-button"));
17
+
18
+ var _modalDialog = _interopRequireWildcard(require("@atlaskit/modal-dialog"));
19
+
20
+ var _reactIntlNext = require("react-intl-next");
21
+
22
+ var _messages = require("../../../messages");
23
+
24
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
25
+
26
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
27
+
28
+ var WarningModal = function WarningModal(props) {
29
+ var isOpen = props.isOpen,
30
+ unsafeLinkText = props.unsafeLinkText,
31
+ url = props.url,
32
+ onClose = props.onClose,
33
+ onContinue = props.onContinue,
34
+ intl = props.intl;
35
+
36
+ var content = /*#__PURE__*/_react.default.createElement(_modalDialog.ModalTransition, null, isOpen && /*#__PURE__*/_react.default.createElement(_modalDialog.default, {
37
+ onClose: onClose,
38
+ testId: "link-with-safety-warning"
39
+ }, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalHeader, null, /*#__PURE__*/_react.default.createElement(_modalDialog.ModalTitle, {
40
+ appearance: "warning"
41
+ }, /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, _messages.messages.check_this_link))), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalBody, null, url && unsafeLinkText && /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, (0, _extends2.default)({}, _messages.messages.link_safety_warning_message, {
42
+ values: {
43
+ unsafeLinkText: unsafeLinkText,
44
+ a: function a() {
45
+ return /*#__PURE__*/_react.default.createElement("a", {
46
+ href: url,
47
+ target: "_blank",
48
+ rel: "noopener noreferrer"
49
+ }, url);
50
+ }
51
+ }
52
+ }))), /*#__PURE__*/_react.default.createElement(_modalDialog.ModalFooter, null, /*#__PURE__*/_react.default.createElement(_standardButton.default, {
53
+ appearance: "subtle",
54
+ onClick: onClose
55
+ }, /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, _messages.messages.go_back)), /*#__PURE__*/_react.default.createElement(_standardButton.default, {
56
+ appearance: "warning",
57
+ onClick: onContinue,
58
+ autoFocus: true
59
+ }, /*#__PURE__*/_react.default.createElement(_reactIntlNext.FormattedMessage, _messages.messages.continue)))));
60
+
61
+ return intl ? content : /*#__PURE__*/_react.default.createElement(_reactIntlNext.IntlProvider, {
62
+ locale: "en"
63
+ }, content);
64
+ };
65
+
66
+ var _default = (0, _reactIntlNext.injectIntl)(WarningModal, {
67
+ enforceContext: false
68
+ });
69
+
70
+ exports.default = _default;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+
10
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
+
12
+ var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
13
+
14
+ var _react = _interopRequireDefault(require("react"));
15
+
16
+ var _useLinkWarningModal2 = require("./LinkWarningModal/hooks/use-link-warning-modal");
17
+
18
+ var _LinkWarningModal = _interopRequireDefault(require("./LinkWarningModal"));
19
+
20
+ var _excluded = ["href", "children", "checkSafety", "onClick", "testId"],
21
+ _excluded2 = ["checkLinkSafety"];
22
+
23
+ var LinkUrl = function LinkUrl(_ref) {
24
+ var href = _ref.href,
25
+ children = _ref.children,
26
+ _ref$checkSafety = _ref.checkSafety,
27
+ checkSafety = _ref$checkSafety === void 0 ? true : _ref$checkSafety,
28
+ _onClick = _ref.onClick,
29
+ _ref$testId = _ref.testId,
30
+ testId = _ref$testId === void 0 ? 'link-with-safety' : _ref$testId,
31
+ props = (0, _objectWithoutProperties2.default)(_ref, _excluded);
32
+
33
+ var _useLinkWarningModal = (0, _useLinkWarningModal2.useLinkWarningModal)(),
34
+ checkLinkSafety = _useLinkWarningModal.checkLinkSafety,
35
+ linkWarningModalProps = (0, _objectWithoutProperties2.default)(_useLinkWarningModal, _excluded2);
36
+
37
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("a", (0, _extends2.default)({
38
+ "data-testid": testId,
39
+ href: href,
40
+ onClick: function onClick(e) {
41
+ checkSafety && checkLinkSafety(e, href);
42
+ _onClick && _onClick(e);
43
+ }
44
+ }, props), children), checkSafety && /*#__PURE__*/_react.default.createElement(_LinkWarningModal.default, linkWarningModalProps));
45
+ };
46
+
47
+ var _default = LinkUrl;
48
+ exports.default = _default;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1 @@
1
+ export { default } from './view/LinkUrl';
@@ -40,6 +40,11 @@ export const messages = defineMessages({
40
40
  defaultMessage: 'Close',
41
41
  description: ''
42
42
  },
43
+ check_this_link: {
44
+ id: 'fabric.linking.check_this_link',
45
+ defaultMessage: 'Check this link',
46
+ description: 'Link safety warning modal header'
47
+ },
43
48
  connect_to: {
44
49
  id: 'fabric.linking.connect_to',
45
50
  defaultMessage: 'Connect to {name}',
@@ -85,6 +90,11 @@ export const messages = defineMessages({
85
90
  defaultMessage: 'Connect to Atlassian to view more details of your work and collaborate from one place.',
86
91
  description: 'Shown when a user does not have access to a link, but can connect their external account to view the link on card view and we do not have the providers name.'
87
92
  },
93
+ continue: {
94
+ id: 'fabric.linking.continue',
95
+ defaultMessage: 'Continue',
96
+ description: 'continue'
97
+ },
88
98
  copy_url_to_clipboard: {
89
99
  id: 'fabric.linking.download',
90
100
  defaultMessage: 'Copy link',
@@ -130,6 +140,11 @@ export const messages = defineMessages({
130
140
  defaultMessage: 'You don’t have access to this preview. Contact the site admin if you need access.',
131
141
  description: 'Informs the user that they cannot view this content.'
132
142
  },
143
+ go_back: {
144
+ id: 'fabric.linking.go_back',
145
+ defaultMessage: 'Go back',
146
+ description: 'go back'
147
+ },
133
148
  invalid_permissions: {
134
149
  id: 'fabric.linking.invalid_permissions',
135
150
  defaultMessage: 'Restricted content',
@@ -335,6 +350,11 @@ export const messages = defineMessages({
335
350
  defaultMessage: 'Try another account',
336
351
  description: 'Allows the user to try an action again with a different account'
337
352
  },
353
+ link_safety_warning_message: {
354
+ id: 'fabric.linking.link_safety_warning_message',
355
+ defaultMessage: 'The link {unsafeLinkText} is taking you to a different site, <a>actual link here</a>',
356
+ description: 'Link safety check warning message'
357
+ },
338
358
  unlink_account: {
339
359
  id: 'fabric.linking.unlink_account',
340
360
  defaultMessage: 'Unlink Account',
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-card",
3
- "version": "23.12.2",
3
+ "version": "23.13.0",
4
4
  "sideEffects": false
5
5
  }
@@ -78,9 +78,14 @@ const FlexibleResolvedView = ({
78
78
  name: ActionName.PreviewAction,
79
79
  hideIcon: true,
80
80
  onClick: () => {
81
+ analytics.ui.actionClickedEvent({
82
+ id: analyticsId,
83
+ actionType: 'PreviewAction',
84
+ display: CardDisplay.Block
85
+ });
81
86
  analytics.operational.invokeSucceededEvent({
82
87
  id: analyticsId,
83
- actionType: 'preview',
88
+ actionType: 'PreviewAction',
84
89
  display: CardDisplay.Block
85
90
  });
86
91
  }
@@ -88,9 +93,14 @@ const FlexibleResolvedView = ({
88
93
  name: ActionName.ViewAction,
89
94
  hideIcon: true,
90
95
  onClick: () => {
96
+ analytics.ui.actionClickedEvent({
97
+ id: analyticsId,
98
+ actionType: 'ViewAction',
99
+ display: CardDisplay.Block
100
+ });
91
101
  analytics.operational.invokeSucceededEvent({
92
102
  id: analyticsId,
93
- actionType: 'view',
103
+ actionType: 'ViewAction',
94
104
  display: CardDisplay.Block
95
105
  });
96
106
  }
@@ -98,9 +108,14 @@ const FlexibleResolvedView = ({
98
108
  name: ActionName.DownloadAction,
99
109
  hideIcon: true,
100
110
  onClick: () => {
111
+ analytics.ui.actionClickedEvent({
112
+ id: analyticsId,
113
+ actionType: 'DownloadAction',
114
+ display: CardDisplay.Block
115
+ });
101
116
  analytics.operational.invokeSucceededEvent({
102
117
  id: analyticsId,
103
- actionType: 'download',
118
+ actionType: 'DownloadAction',
104
119
  display: CardDisplay.Block
105
120
  });
106
121
  }
@@ -56,7 +56,7 @@ const Action = ({
56
56
  overrideCss: overrideCss,
57
57
  size: size,
58
58
  testId: testId,
59
- tooltipMessage: tooltipMessage
59
+ tooltipMessage: tooltipMessage || content
60
60
  });
61
61
  }
62
62
  };
@@ -0,0 +1,49 @@
1
+ import { useState } from 'react';
2
+ import { normalizeUrl } from '@atlaskit/linking-common/url';
3
+ export const useLinkWarningModal = () => {
4
+ const [unsafeLinkText, setUnsafeLinkText] = useState(null);
5
+ const [url, setUrl] = useState(null);
6
+ const [isOpen, setIsOpen] = useState(false);
7
+ /**
8
+ * It checks and warns if a link text is a URL and different from an actual link destination
9
+ */
10
+
11
+ const checkLinkSafety = (event, href) => {
12
+ const linkText = event.currentTarget.innerText;
13
+ const anchorLinkRegex = new RegExp(/^#/im);
14
+ const isAnchorLink = anchorLinkRegex.test(linkText);
15
+
16
+ if (isAnchorLink) {
17
+ return;
18
+ }
19
+
20
+ const normalisedUrlFromLinkText = normalizeUrl(linkText);
21
+
22
+ if (!!normalisedUrlFromLinkText && normalisedUrlFromLinkText !== href) {
23
+ event.preventDefault();
24
+ setUnsafeLinkText(linkText);
25
+ href && setUrl(href);
26
+ setIsOpen(true);
27
+ }
28
+ };
29
+
30
+ const onClose = () => {
31
+ setIsOpen(false);
32
+ setUnsafeLinkText(null);
33
+ setUrl(null);
34
+ };
35
+
36
+ const onContinue = () => {
37
+ onClose();
38
+ url && window.location.assign(url);
39
+ };
40
+
41
+ return {
42
+ checkLinkSafety,
43
+ unsafeLinkText,
44
+ onClose,
45
+ onContinue,
46
+ isOpen,
47
+ url
48
+ };
49
+ };
@@ -0,0 +1,46 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React from 'react';
3
+ import Button from '@atlaskit/button/standard-button';
4
+ import Modal, { ModalBody, ModalFooter, ModalHeader, ModalTitle, ModalTransition } from '@atlaskit/modal-dialog';
5
+ import { FormattedMessage, IntlProvider, injectIntl } from 'react-intl-next';
6
+ import { messages } from '../../../messages';
7
+
8
+ const WarningModal = props => {
9
+ const {
10
+ isOpen,
11
+ unsafeLinkText,
12
+ url,
13
+ onClose,
14
+ onContinue,
15
+ intl
16
+ } = props;
17
+ const content = /*#__PURE__*/React.createElement(ModalTransition, null, isOpen && /*#__PURE__*/React.createElement(Modal, {
18
+ onClose: onClose,
19
+ testId: "link-with-safety-warning"
20
+ }, /*#__PURE__*/React.createElement(ModalHeader, null, /*#__PURE__*/React.createElement(ModalTitle, {
21
+ appearance: "warning"
22
+ }, /*#__PURE__*/React.createElement(FormattedMessage, messages.check_this_link))), /*#__PURE__*/React.createElement(ModalBody, null, url && unsafeLinkText && /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages.link_safety_warning_message, {
23
+ values: {
24
+ unsafeLinkText: unsafeLinkText,
25
+ a: () => /*#__PURE__*/React.createElement("a", {
26
+ href: url,
27
+ target: "_blank",
28
+ rel: "noopener noreferrer"
29
+ }, url)
30
+ }
31
+ }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
32
+ appearance: "subtle",
33
+ onClick: onClose
34
+ }, /*#__PURE__*/React.createElement(FormattedMessage, messages.go_back)), /*#__PURE__*/React.createElement(Button, {
35
+ appearance: "warning",
36
+ onClick: onContinue,
37
+ autoFocus: true
38
+ }, /*#__PURE__*/React.createElement(FormattedMessage, messages.continue)))));
39
+ return intl ? content : /*#__PURE__*/React.createElement(IntlProvider, {
40
+ locale: "en"
41
+ }, content);
42
+ };
43
+
44
+ export default injectIntl(WarningModal, {
45
+ enforceContext: false
46
+ });
@@ -0,0 +1,28 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React from 'react';
3
+ import { useLinkWarningModal } from './LinkWarningModal/hooks/use-link-warning-modal';
4
+ import LinkWarningModal from './LinkWarningModal';
5
+
6
+ const LinkUrl = ({
7
+ href,
8
+ children,
9
+ checkSafety = true,
10
+ onClick,
11
+ testId = 'link-with-safety',
12
+ ...props
13
+ }) => {
14
+ const {
15
+ checkLinkSafety,
16
+ ...linkWarningModalProps
17
+ } = useLinkWarningModal();
18
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("a", _extends({
19
+ "data-testid": testId,
20
+ href: href,
21
+ onClick: e => {
22
+ checkSafety && checkLinkSafety(e, href);
23
+ onClick && onClick(e);
24
+ }
25
+ }, props), children), checkSafety && /*#__PURE__*/React.createElement(LinkWarningModal, linkWarningModalProps));
26
+ };
27
+
28
+ export default LinkUrl;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export { default } from './view/LinkUrl';
@@ -40,6 +40,11 @@ export var messages = defineMessages({
40
40
  defaultMessage: 'Close',
41
41
  description: ''
42
42
  },
43
+ check_this_link: {
44
+ id: 'fabric.linking.check_this_link',
45
+ defaultMessage: 'Check this link',
46
+ description: 'Link safety warning modal header'
47
+ },
43
48
  connect_to: {
44
49
  id: 'fabric.linking.connect_to',
45
50
  defaultMessage: 'Connect to {name}',
@@ -85,6 +90,11 @@ export var messages = defineMessages({
85
90
  defaultMessage: 'Connect to Atlassian to view more details of your work and collaborate from one place.',
86
91
  description: 'Shown when a user does not have access to a link, but can connect their external account to view the link on card view and we do not have the providers name.'
87
92
  },
93
+ continue: {
94
+ id: 'fabric.linking.continue',
95
+ defaultMessage: 'Continue',
96
+ description: 'continue'
97
+ },
88
98
  copy_url_to_clipboard: {
89
99
  id: 'fabric.linking.download',
90
100
  defaultMessage: 'Copy link',
@@ -130,6 +140,11 @@ export var messages = defineMessages({
130
140
  defaultMessage: 'You don’t have access to this preview. Contact the site admin if you need access.',
131
141
  description: 'Informs the user that they cannot view this content.'
132
142
  },
143
+ go_back: {
144
+ id: 'fabric.linking.go_back',
145
+ defaultMessage: 'Go back',
146
+ description: 'go back'
147
+ },
133
148
  invalid_permissions: {
134
149
  id: 'fabric.linking.invalid_permissions',
135
150
  defaultMessage: 'Restricted content',
@@ -335,6 +350,11 @@ export var messages = defineMessages({
335
350
  defaultMessage: 'Try another account',
336
351
  description: 'Allows the user to try an action again with a different account'
337
352
  },
353
+ link_safety_warning_message: {
354
+ id: 'fabric.linking.link_safety_warning_message',
355
+ defaultMessage: 'The link {unsafeLinkText} is taking you to a different site, <a>actual link here</a>',
356
+ description: 'Link safety check warning message'
357
+ },
338
358
  unlink_account: {
339
359
  id: 'fabric.linking.unlink_account',
340
360
  defaultMessage: 'Unlink Account',
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-card",
3
- "version": "23.12.2",
3
+ "version": "23.13.0",
4
4
  "sideEffects": false
5
5
  }
@@ -90,9 +90,14 @@ var FlexibleResolvedView = function FlexibleResolvedView(_ref) {
90
90
  name: ActionName.PreviewAction,
91
91
  hideIcon: true,
92
92
  onClick: function onClick() {
93
+ analytics.ui.actionClickedEvent({
94
+ id: analyticsId,
95
+ actionType: 'PreviewAction',
96
+ display: CardDisplay.Block
97
+ });
93
98
  analytics.operational.invokeSucceededEvent({
94
99
  id: analyticsId,
95
- actionType: 'preview',
100
+ actionType: 'PreviewAction',
96
101
  display: CardDisplay.Block
97
102
  });
98
103
  }
@@ -100,9 +105,14 @@ var FlexibleResolvedView = function FlexibleResolvedView(_ref) {
100
105
  name: ActionName.ViewAction,
101
106
  hideIcon: true,
102
107
  onClick: function onClick() {
108
+ analytics.ui.actionClickedEvent({
109
+ id: analyticsId,
110
+ actionType: 'ViewAction',
111
+ display: CardDisplay.Block
112
+ });
103
113
  analytics.operational.invokeSucceededEvent({
104
114
  id: analyticsId,
105
- actionType: 'view',
115
+ actionType: 'ViewAction',
106
116
  display: CardDisplay.Block
107
117
  });
108
118
  }
@@ -110,9 +120,14 @@ var FlexibleResolvedView = function FlexibleResolvedView(_ref) {
110
120
  name: ActionName.DownloadAction,
111
121
  hideIcon: true,
112
122
  onClick: function onClick() {
123
+ analytics.ui.actionClickedEvent({
124
+ id: analyticsId,
125
+ actionType: 'DownloadAction',
126
+ display: CardDisplay.Block
127
+ });
113
128
  analytics.operational.invokeSucceededEvent({
114
129
  id: analyticsId,
115
- actionType: 'download',
130
+ actionType: 'DownloadAction',
116
131
  display: CardDisplay.Block
117
132
  });
118
133
  }
@@ -60,7 +60,7 @@ var Action = function Action(_ref) {
60
60
  overrideCss: overrideCss,
61
61
  size: size,
62
62
  testId: testId,
63
- tooltipMessage: tooltipMessage
63
+ tooltipMessage: tooltipMessage || content
64
64
  });
65
65
  }
66
66
  };
@@ -0,0 +1,62 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import { useState } from 'react';
3
+ import { normalizeUrl } from '@atlaskit/linking-common/url';
4
+ export var useLinkWarningModal = function useLinkWarningModal() {
5
+ var _useState = useState(null),
6
+ _useState2 = _slicedToArray(_useState, 2),
7
+ unsafeLinkText = _useState2[0],
8
+ setUnsafeLinkText = _useState2[1];
9
+
10
+ var _useState3 = useState(null),
11
+ _useState4 = _slicedToArray(_useState3, 2),
12
+ url = _useState4[0],
13
+ setUrl = _useState4[1];
14
+
15
+ var _useState5 = useState(false),
16
+ _useState6 = _slicedToArray(_useState5, 2),
17
+ isOpen = _useState6[0],
18
+ setIsOpen = _useState6[1];
19
+ /**
20
+ * It checks and warns if a link text is a URL and different from an actual link destination
21
+ */
22
+
23
+
24
+ var checkLinkSafety = function checkLinkSafety(event, href) {
25
+ var linkText = event.currentTarget.innerText;
26
+ var anchorLinkRegex = new RegExp(/^#/im);
27
+ var isAnchorLink = anchorLinkRegex.test(linkText);
28
+
29
+ if (isAnchorLink) {
30
+ return;
31
+ }
32
+
33
+ var normalisedUrlFromLinkText = normalizeUrl(linkText);
34
+
35
+ if (!!normalisedUrlFromLinkText && normalisedUrlFromLinkText !== href) {
36
+ event.preventDefault();
37
+ setUnsafeLinkText(linkText);
38
+ href && setUrl(href);
39
+ setIsOpen(true);
40
+ }
41
+ };
42
+
43
+ var onClose = function onClose() {
44
+ setIsOpen(false);
45
+ setUnsafeLinkText(null);
46
+ setUrl(null);
47
+ };
48
+
49
+ var onContinue = function onContinue() {
50
+ onClose();
51
+ url && window.location.assign(url);
52
+ };
53
+
54
+ return {
55
+ checkLinkSafety: checkLinkSafety,
56
+ unsafeLinkText: unsafeLinkText,
57
+ onClose: onClose,
58
+ onContinue: onContinue,
59
+ isOpen: isOpen,
60
+ url: url
61
+ };
62
+ };
@@ -0,0 +1,46 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import React from 'react';
3
+ import Button from '@atlaskit/button/standard-button';
4
+ import Modal, { ModalBody, ModalFooter, ModalHeader, ModalTitle, ModalTransition } from '@atlaskit/modal-dialog';
5
+ import { FormattedMessage, IntlProvider, injectIntl } from 'react-intl-next';
6
+ import { messages } from '../../../messages';
7
+
8
+ var WarningModal = function WarningModal(props) {
9
+ var isOpen = props.isOpen,
10
+ unsafeLinkText = props.unsafeLinkText,
11
+ url = props.url,
12
+ onClose = props.onClose,
13
+ onContinue = props.onContinue,
14
+ intl = props.intl;
15
+ var content = /*#__PURE__*/React.createElement(ModalTransition, null, isOpen && /*#__PURE__*/React.createElement(Modal, {
16
+ onClose: onClose,
17
+ testId: "link-with-safety-warning"
18
+ }, /*#__PURE__*/React.createElement(ModalHeader, null, /*#__PURE__*/React.createElement(ModalTitle, {
19
+ appearance: "warning"
20
+ }, /*#__PURE__*/React.createElement(FormattedMessage, messages.check_this_link))), /*#__PURE__*/React.createElement(ModalBody, null, url && unsafeLinkText && /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages.link_safety_warning_message, {
21
+ values: {
22
+ unsafeLinkText: unsafeLinkText,
23
+ a: function a() {
24
+ return /*#__PURE__*/React.createElement("a", {
25
+ href: url,
26
+ target: "_blank",
27
+ rel: "noopener noreferrer"
28
+ }, url);
29
+ }
30
+ }
31
+ }))), /*#__PURE__*/React.createElement(ModalFooter, null, /*#__PURE__*/React.createElement(Button, {
32
+ appearance: "subtle",
33
+ onClick: onClose
34
+ }, /*#__PURE__*/React.createElement(FormattedMessage, messages.go_back)), /*#__PURE__*/React.createElement(Button, {
35
+ appearance: "warning",
36
+ onClick: onContinue,
37
+ autoFocus: true
38
+ }, /*#__PURE__*/React.createElement(FormattedMessage, messages.continue)))));
39
+ return intl ? content : /*#__PURE__*/React.createElement(IntlProvider, {
40
+ locale: "en"
41
+ }, content);
42
+ };
43
+
44
+ export default injectIntl(WarningModal, {
45
+ enforceContext: false
46
+ });
@@ -0,0 +1,33 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
+ var _excluded = ["href", "children", "checkSafety", "onClick", "testId"],
4
+ _excluded2 = ["checkLinkSafety"];
5
+ import React from 'react';
6
+ import { useLinkWarningModal } from './LinkWarningModal/hooks/use-link-warning-modal';
7
+ import LinkWarningModal from './LinkWarningModal';
8
+
9
+ var LinkUrl = function LinkUrl(_ref) {
10
+ var href = _ref.href,
11
+ children = _ref.children,
12
+ _ref$checkSafety = _ref.checkSafety,
13
+ checkSafety = _ref$checkSafety === void 0 ? true : _ref$checkSafety,
14
+ _onClick = _ref.onClick,
15
+ _ref$testId = _ref.testId,
16
+ testId = _ref$testId === void 0 ? 'link-with-safety' : _ref$testId,
17
+ props = _objectWithoutProperties(_ref, _excluded);
18
+
19
+ var _useLinkWarningModal = useLinkWarningModal(),
20
+ checkLinkSafety = _useLinkWarningModal.checkLinkSafety,
21
+ linkWarningModalProps = _objectWithoutProperties(_useLinkWarningModal, _excluded2);
22
+
23
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("a", _extends({
24
+ "data-testid": testId,
25
+ href: href,
26
+ onClick: function onClick(e) {
27
+ checkSafety && checkLinkSafety(e, href);
28
+ _onClick && _onClick(e);
29
+ }
30
+ }, props), children), checkSafety && /*#__PURE__*/React.createElement(LinkWarningModal, linkWarningModalProps));
31
+ };
32
+
33
+ export default LinkUrl;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { default } from './view/LinkUrl';
2
+ export type { LinkUrlProps } from './view/LinkUrl/types';
@@ -1,6 +1,6 @@
1
1
  import { MessageDescriptor } from 'react-intl-next';
2
2
  export declare type RequestAccessMessageKey = 'click_to_join' | 'click_to_join_description' | 'forbidden_description' | 'request_access' | 'request_access_description' | 'request_access_pending' | 'request_access_pending_description' | 'request_denied_description';
3
- export declare type MessageKey = 'cannot_find_link' | 'connect_link_account_card' | 'connect_link_account_card_name' | 'connect_link_account_card_description' | 'connect_unauthorised_account_action' | 'connect_unauthorised_account_description' | 'connect_unauthorised_account_description_no_provider' | 'copy_url_to_clipboard' | 'could_not_load_link' | 'download' | 'invalid_permissions' | 'invalid_permissions_description' | 'join_to_view' | 'connect_link_account' | 'created_by' | 'created_on_relative' | 'created_on_absolute' | 'delete' | 'edit' | 'learn_more_about_smart_links' | 'loading' | 'modified_by' | 'modified_on_relative' | 'modified_on_absolute' | 'more_actions' | 'not_found_title' | 'not_found_description' | 'open_link_in_a_new_tab' | 'preview' | 'preview_close' | 'preview_max_size' | 'preview_min_size' | 'priority_blocker' | 'priority_critical' | 'priority_high' | 'priority_highest' | 'priority_low' | 'priority_lowest' | 'priority_major' | 'priority_medium' | 'priority_minor' | 'priority_trivial' | 'priority_undefined' | 'forbidden_access' | 'pending_request' | 'restricted_link' | 'request_access_to_view' | 'request_denied' | 'try_again' | 'try_another_account' | 'view' | 'viewIn' | 'viewOriginal' | 'actions' | 'add_account' | 'cancel' | 'close' | 'connect_to' | 'connect_account_description' | 'retry' | 'save' | 'unlink_account' | RequestAccessMessageKey;
3
+ export declare type MessageKey = 'cannot_find_link' | 'connect_link_account_card' | 'connect_link_account_card_name' | 'connect_link_account_card_description' | 'connect_unauthorised_account_action' | 'connect_unauthorised_account_description' | 'connect_unauthorised_account_description_no_provider' | 'continue' | 'copy_url_to_clipboard' | 'could_not_load_link' | 'download' | 'go_back' | 'invalid_permissions' | 'invalid_permissions_description' | 'join_to_view' | 'connect_link_account' | 'created_by' | 'created_on_relative' | 'created_on_absolute' | 'check_this_link' | 'delete' | 'edit' | 'learn_more_about_smart_links' | 'loading' | 'link_safety_warning_message' | 'modified_by' | 'modified_on_relative' | 'modified_on_absolute' | 'more_actions' | 'not_found_title' | 'not_found_description' | 'open_link_in_a_new_tab' | 'preview' | 'preview_close' | 'preview_max_size' | 'preview_min_size' | 'priority_blocker' | 'priority_critical' | 'priority_high' | 'priority_highest' | 'priority_low' | 'priority_lowest' | 'priority_major' | 'priority_medium' | 'priority_minor' | 'priority_trivial' | 'priority_undefined' | 'forbidden_access' | 'pending_request' | 'restricted_link' | 'request_access_to_view' | 'request_denied' | 'try_again' | 'try_another_account' | 'view' | 'viewIn' | 'viewOriginal' | 'actions' | 'add_account' | 'cancel' | 'close' | 'connect_to' | 'connect_account_description' | 'retry' | 'save' | 'unlink_account' | RequestAccessMessageKey;
4
4
  declare type Messages = {
5
5
  [K in MessageKey]: MessageDescriptor;
6
6
  };
@@ -0,0 +1,9 @@
1
+ import { MouseEvent } from 'react';
2
+ export declare const useLinkWarningModal: () => {
3
+ checkLinkSafety: (event: MouseEvent, href: string | undefined) => void;
4
+ unsafeLinkText: string | null;
5
+ onClose: () => void;
6
+ onContinue: () => void;
7
+ isOpen: boolean;
8
+ url: string | null;
9
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { WrappedComponentProps } from 'react-intl-next';
3
+ interface LinkWarningModalProps {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ onContinue: () => void;
7
+ unsafeLinkText: string | null;
8
+ url: string | null;
9
+ }
10
+ declare const _default: React.FC<import("react-intl-next").WithIntlProps<LinkWarningModalProps & WrappedComponentProps<"intl">>> & {
11
+ WrappedComponent: React.ComponentType<LinkWarningModalProps & WrappedComponentProps<"intl">>;
12
+ };
13
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import { LinkUrlProps } from './types';
3
+ declare const LinkUrl: React.FC<LinkUrlProps>;
4
+ export default LinkUrl;
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ export interface LinkUrlProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
3
+ /**
4
+ * Determines if we want to perform a link safety check. True by default.
5
+ */
6
+ checkSafety?: boolean;
7
+ testId?: string;
8
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@atlaskit/smart-card/link-url",
3
+ "main": "../dist/cjs/linkUrl.js",
4
+ "module": "../dist/esm/linkUrl.js",
5
+ "module:es2019": "../dist/es2019/linkUrl.js",
6
+ "sideEffects": false,
7
+ "types": "../dist/types/linkUrl.d.ts",
8
+ "typesVersions": {
9
+ ">=4.0 <4.5": {
10
+ "*": [
11
+ "../dist/types-ts4.0/linkUrl.d.ts"
12
+ ]
13
+ }
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-card",
3
- "version": "23.12.2",
3
+ "version": "23.13.0",
4
4
  "description": "Smart card component",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -120,6 +120,7 @@
120
120
  "./types": "./src/types.ts",
121
121
  "./hooks": "./src/hooks.ts",
122
122
  "./hover-card": "./src/hoverCard.ts",
123
+ "./link-url": "./src/linkUrl.ts",
123
124
  ".": "./src/index.ts"
124
125
  },
125
126
  "prettier": "@atlassian/atlassian-frontend-prettier-config-1.0.1",