@atlaskit/link-picker 1.9.2 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/analytics.codegen.js +1 -1
- package/dist/cjs/common/analytics/context.js +137 -0
- package/dist/cjs/common/analytics/index.js +27 -0
- package/dist/cjs/common/analytics/input-field-tracking.js +115 -0
- package/dist/cjs/common/constants.js +1 -1
- package/dist/cjs/ui/error-boundary/index.js +104 -0
- package/dist/cjs/ui/index.js +10 -6
- package/dist/cjs/ui/link-picker/index.js +56 -16
- package/dist/cjs/ui/link-picker/text-input/index.js +18 -23
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/analytics.codegen.js +1 -1
- package/dist/es2019/common/analytics/context.js +95 -0
- package/dist/es2019/common/analytics/index.js +2 -0
- package/dist/es2019/common/analytics/input-field-tracking.js +85 -0
- package/dist/es2019/common/constants.js +1 -1
- package/dist/es2019/ui/error-boundary/index.js +62 -0
- package/dist/es2019/ui/index.js +8 -5
- package/dist/es2019/ui/link-picker/index.js +51 -14
- package/dist/es2019/ui/link-picker/link-search-error/index.js +2 -2
- package/dist/es2019/ui/link-picker/text-input/index.js +14 -20
- package/dist/es2019/version.json +1 -1
- package/dist/esm/analytics.codegen.js +1 -1
- package/dist/esm/common/analytics/context.js +110 -0
- package/dist/esm/common/analytics/index.js +2 -0
- package/dist/esm/common/analytics/input-field-tracking.js +91 -0
- package/dist/esm/common/constants.js +1 -1
- package/dist/esm/ui/error-boundary/index.js +81 -0
- package/dist/esm/ui/index.js +8 -5
- package/dist/esm/ui/link-picker/index.js +54 -14
- package/dist/esm/ui/link-picker/link-search-error/index.js +2 -2
- package/dist/esm/ui/link-picker/text-input/index.js +16 -21
- package/dist/esm/version.json +1 -1
- package/dist/types/analytics.codegen.d.ts +21 -1
- package/dist/types/common/analytics/context.d.ts +28 -0
- package/dist/types/common/analytics/index.d.ts +2 -0
- package/dist/types/common/analytics/input-field-tracking.d.ts +17 -0
- package/dist/types/common/constants.d.ts +1 -1
- package/dist/types/ui/error-boundary/index.d.ts +27 -0
- package/dist/types/ui/index.d.ts +2 -5
- package/dist/types/ui/link-picker/text-input/index.d.ts +6 -8
- package/package.json +1 -1
- package/dist/cjs/services/with-link-picker-analytics/index.js +0 -33
- package/dist/es2019/services/with-link-picker-analytics/index.js +0 -15
- package/dist/esm/services/with-link-picker-analytics/index.js +0 -17
- package/dist/types/services/with-link-picker-analytics/index.d.ts +0 -4
|
@@ -25,24 +25,23 @@ var _selectClear = _interopRequireDefault(require("@atlaskit/icon/glyph/select-c
|
|
|
25
25
|
|
|
26
26
|
var _utils = require("../utils");
|
|
27
27
|
|
|
28
|
-
var _styled = require("./styled");
|
|
29
|
-
|
|
30
28
|
var _ = require("../");
|
|
31
29
|
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
var
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
30
|
+
var _styled = require("./styled");
|
|
31
|
+
|
|
32
|
+
var _excluded = ["name", "label", "autoFocus", "onRedo", "onUndo", "onKeyDown", "onClear", "clearLabel", "error"];
|
|
33
|
+
|
|
34
|
+
var TextInput = function TextInput(_ref) {
|
|
35
|
+
var name = _ref.name,
|
|
36
|
+
label = _ref.label,
|
|
37
|
+
autoFocus = _ref.autoFocus,
|
|
38
|
+
onRedo = _ref.onRedo,
|
|
39
|
+
onUndo = _ref.onUndo,
|
|
40
|
+
onKeyDown = _ref.onKeyDown,
|
|
41
|
+
onClear = _ref.onClear,
|
|
42
|
+
clearLabel = _ref.clearLabel,
|
|
43
|
+
error = _ref.error,
|
|
44
|
+
restProps = (0, _objectWithoutProperties2.default)(_ref, _excluded);
|
|
46
45
|
var inputRef = (0, _react.useRef)(null);
|
|
47
46
|
var handleRef = (0, _react.useCallback)(function (input) {
|
|
48
47
|
if (input) {
|
|
@@ -56,9 +55,6 @@ var PanelTextInput = function PanelTextInput(props) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
}, [autoFocus]);
|
|
59
|
-
var handleChange = (0, _react.useCallback)(function (e) {
|
|
60
|
-
onChange(e.currentTarget.value);
|
|
61
|
-
}, [onChange]);
|
|
62
58
|
var handleKeydown = (0, _react.useCallback)(function (e) {
|
|
63
59
|
if (typeof onUndo === 'function' && (0, _utils.isUndoEvent)(e)) {
|
|
64
60
|
e.preventDefault();
|
|
@@ -96,11 +92,10 @@ var PanelTextInput = function PanelTextInput(props) {
|
|
|
96
92
|
}, (0, _react2.jsx)(_form.Field, {
|
|
97
93
|
label: label,
|
|
98
94
|
name: name
|
|
99
|
-
}, function (
|
|
100
|
-
var fieldProps =
|
|
95
|
+
}, function (_ref2) {
|
|
96
|
+
var fieldProps = _ref2.fieldProps;
|
|
101
97
|
return (0, _react2.jsx)(_react.Fragment, null, (0, _react2.jsx)(_textfield.default, (0, _extends2.default)({}, fieldProps, restProps, {
|
|
102
98
|
onKeyDown: handleKeydown,
|
|
103
|
-
onChange: handleChange,
|
|
104
99
|
ref: handleRef,
|
|
105
100
|
elemAfterInput: clearText,
|
|
106
101
|
isInvalid: !!error
|
|
@@ -110,5 +105,5 @@ var PanelTextInput = function PanelTextInput(props) {
|
|
|
110
105
|
}));
|
|
111
106
|
};
|
|
112
107
|
|
|
113
|
-
var _default =
|
|
108
|
+
var _default = TextInput;
|
|
114
109
|
exports.default = _default;
|
package/dist/cjs/version.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates analytics utilities from the package analytics spec yaml
|
|
5
5
|
*
|
|
6
|
-
* @codegen <<SignedSource::
|
|
6
|
+
* @codegen <<SignedSource::3c46f1d8f463b6898a7a0e1e1f4676b4>>
|
|
7
7
|
* @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
|
|
8
8
|
*/
|
|
9
9
|
function createEventPayload(eventKey, attributes) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { AnalyticsListener } from '@atlaskit/analytics-next';
|
|
4
|
+
import { ANALYTICS_CHANNEL } from '../constants';
|
|
5
|
+
import { normalizeUrl } from '../utils/url';
|
|
6
|
+
const DEFAULT_CONTEXT_ATTRIBUTES = {
|
|
7
|
+
linkState: 'newLink',
|
|
8
|
+
linkFieldContent: null,
|
|
9
|
+
linkFieldContentInputMethod: null,
|
|
10
|
+
displayTextFieldContent: null,
|
|
11
|
+
displayTextFieldContentInputMethod: null
|
|
12
|
+
};
|
|
13
|
+
const LinkPickerAnalyticsContext = /*#__PURE__*/React.createContext({
|
|
14
|
+
trackAttribute: () => {},
|
|
15
|
+
trackAttributes: () => {},
|
|
16
|
+
getAttributes: () => DEFAULT_CONTEXT_ATTRIBUTES
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* TODO: Should we hook this into the form state/reducer?
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const contextAttributesFromIntialProps = props => {
|
|
23
|
+
return {
|
|
24
|
+
linkState: normalizeUrl(props.url) ? 'editLink' : 'newLink',
|
|
25
|
+
linkFieldContent: normalizeUrl(props.url) ? 'url' : null,
|
|
26
|
+
displayTextFieldContent: Boolean(props.displayText) ? 'text_string' : null
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Context for tracking attributes to deliver at a context-level
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const LinkPickerAnalytics = ({
|
|
35
|
+
initialContext,
|
|
36
|
+
children
|
|
37
|
+
}) => {
|
|
38
|
+
const dataRef = useRef({ ...DEFAULT_CONTEXT_ATTRIBUTES,
|
|
39
|
+
...initialContext
|
|
40
|
+
});
|
|
41
|
+
const methods = useMemo(() => ({
|
|
42
|
+
trackAttribute: (key, value) => {
|
|
43
|
+
dataRef.current[key] = value;
|
|
44
|
+
},
|
|
45
|
+
trackAttributes: attributes => {
|
|
46
|
+
dataRef.current = { ...dataRef.current,
|
|
47
|
+
...attributes
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
getAttributes: () => dataRef.current
|
|
51
|
+
}), []);
|
|
52
|
+
return /*#__PURE__*/React.createElement(LinkPickerAnalyticsContext.Provider, {
|
|
53
|
+
value: methods
|
|
54
|
+
}, children);
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Hook that exposes the context-level attribute getters and setters.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export const useLinkPickerAnalytics = () => useContext(LinkPickerAnalyticsContext);
|
|
62
|
+
/**
|
|
63
|
+
* Wrap component in "attributes" context store and initialise the initial context attributes from props.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
function withLinkPickerAnalytics(WrappedComponent) {
|
|
67
|
+
return props => {
|
|
68
|
+
const [initialContext] = useState(() => contextAttributesFromIntialProps(props));
|
|
69
|
+
return /*#__PURE__*/React.createElement(LinkPickerAnalytics, _extends({
|
|
70
|
+
initialContext: initialContext
|
|
71
|
+
}, props), /*#__PURE__*/React.createElement(WrappedComponent, props));
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Wraps a component with the analytics context + listener to update events with contextual-level attributes.
|
|
76
|
+
* Should be implemented once at the root of the link picker.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
export function withLinkPickerAnalyticsContext(WrappedComponent) {
|
|
81
|
+
return withLinkPickerAnalytics(props => {
|
|
82
|
+
const {
|
|
83
|
+
getAttributes
|
|
84
|
+
} = useLinkPickerAnalytics();
|
|
85
|
+
const onEvent = useCallback(event => {
|
|
86
|
+
event.update({
|
|
87
|
+
attributes: getAttributes()
|
|
88
|
+
});
|
|
89
|
+
}, [getAttributes]);
|
|
90
|
+
return /*#__PURE__*/React.createElement(AnalyticsListener, {
|
|
91
|
+
channel: ANALYTICS_CHANNEL,
|
|
92
|
+
onEvent: onEvent
|
|
93
|
+
}, /*#__PURE__*/React.createElement(WrappedComponent, props));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { useCallback, useRef } from 'react';
|
|
3
|
+
import { useAnalyticsEvents } from '@atlaskit/analytics-next';
|
|
4
|
+
import { ANALYTICS_CHANNEL } from '../constants';
|
|
5
|
+
import createEventPayload from '../../analytics.codegen';
|
|
6
|
+
import { useLinkPickerAnalytics } from '.';
|
|
7
|
+
|
|
8
|
+
const isEventWithInputType = e => {
|
|
9
|
+
return typeof e === 'object' && e !== null && 'inputType' in e;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getChangeEventInputMethodType = event => {
|
|
13
|
+
if (isEventWithInputType(event.nativeEvent) && typeof event.nativeEvent.inputType === 'string' && event.nativeEvent.inputType.includes('insertFromPaste')) {
|
|
14
|
+
return 'paste';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return 'manual';
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Wraps an input field and connects it to the link picker analytics context to provide a baseline of analytics tracking
|
|
21
|
+
* @param WrappedComponent The input field to be tracked
|
|
22
|
+
* @param field The name of the field (ie matching actionSubjectId)
|
|
23
|
+
* @param filterOnChangeEventAttrs Function to hook into and filter/customise the default attribute tracking logic
|
|
24
|
+
* @returns HOC Input field with tracking
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export const withInputFieldTracking = (WrappedComponent, field, filterOnChangeEventAttrs = (_, attributes) => attributes // TODO: Consider refactor to use template literal type if it makes these more usable / safe / dev friendly
|
|
29
|
+
// contentTypeFn: (value: string) => LinkPickerAnalyticsContextType[`${Field}FieldContent`],
|
|
30
|
+
) => {
|
|
31
|
+
return props => {
|
|
32
|
+
const {
|
|
33
|
+
onBlur,
|
|
34
|
+
onChange,
|
|
35
|
+
onClear,
|
|
36
|
+
onFocus
|
|
37
|
+
} = props;
|
|
38
|
+
const {
|
|
39
|
+
trackAttribute,
|
|
40
|
+
trackAttributes
|
|
41
|
+
} = useLinkPickerAnalytics();
|
|
42
|
+
const {
|
|
43
|
+
createAnalyticsEvent
|
|
44
|
+
} = useAnalyticsEvents(); // Ref to track if the value of the field has changed between focus and blur
|
|
45
|
+
|
|
46
|
+
const valueOnFocus = useRef(null); // Track the content and input method on every direct change to the input
|
|
47
|
+
|
|
48
|
+
const handleOnChange = useCallback(event => {
|
|
49
|
+
// TODO: See above re template literal, improve typesafety here
|
|
50
|
+
// trackAttribute(`${field}FieldContent`, contentTypeFn(event.currentTarget.value));
|
|
51
|
+
// trackAttribute(`${field}FieldContentInputMethod`, methodTypeFn(event));
|
|
52
|
+
trackAttributes(filterOnChangeEventAttrs(event, {
|
|
53
|
+
[`${field}FieldContent`]: Boolean(event.currentTarget.value) ? 'text_string' : null,
|
|
54
|
+
[`${field}FieldContentInputMethod`]: getChangeEventInputMethodType(event)
|
|
55
|
+
}));
|
|
56
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
57
|
+
}, [onChange, trackAttributes]); // Store the value of the field at the time the field is focused so we can know if it changes when the user's focus leaves
|
|
58
|
+
|
|
59
|
+
const handleOnFocus = useCallback(event => {
|
|
60
|
+
valueOnFocus.current = event.currentTarget.value;
|
|
61
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
|
|
62
|
+
}, [onFocus]); // Fire updated events only when the user's focus leaves the field
|
|
63
|
+
|
|
64
|
+
const handleOnBlur = useCallback(event => {
|
|
65
|
+
// Only fire if the value has changed since the field was focused (we are only concerned with the net change to the field)
|
|
66
|
+
if (event.currentTarget.value !== valueOnFocus.current) {
|
|
67
|
+
createAnalyticsEvent(createEventPayload(`ui.textField.updated.${field}Field`, {})).fire(ANALYTICS_CHANNEL);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
71
|
+
}, [onBlur, createAnalyticsEvent]); // Track the values as null when the text field is cleared
|
|
72
|
+
|
|
73
|
+
const handleOnClear = useCallback((...args) => {
|
|
74
|
+
trackAttribute(`${field}FieldContent`, null);
|
|
75
|
+
trackAttribute(`${field}FieldContentInputMethod`, null);
|
|
76
|
+
onClear === null || onClear === void 0 ? void 0 : onClear(...args);
|
|
77
|
+
}, [onClear, trackAttribute]);
|
|
78
|
+
return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
|
|
79
|
+
onBlur: handleOnBlur,
|
|
80
|
+
onChange: handleOnChange,
|
|
81
|
+
onClear: handleOnClear,
|
|
82
|
+
onFocus: handleOnFocus
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const ANALYTICS_CHANNEL = 'media';
|
|
2
|
-
export const COMPONENT_NAME = '
|
|
2
|
+
export const COMPONENT_NAME = 'linkPicker';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import React, { Component } from 'react';
|
|
3
|
+
import { withAnalyticsEvents } from '@atlaskit/analytics-next';
|
|
4
|
+
import createEventPayload from '../../analytics.codegen';
|
|
5
|
+
// eslint-disable-next-line @repo/internal/react/no-class-components
|
|
6
|
+
export class BaseErrorBoundary extends Component {
|
|
7
|
+
constructor(props) {
|
|
8
|
+
super(props);
|
|
9
|
+
|
|
10
|
+
_defineProperty(this, "fireAnalytics", analyticsErrorPayload => {
|
|
11
|
+
var _window, _window$navigator, _analyticsErrorPayloa, _analyticsErrorPayloa2;
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
createAnalyticsEvent,
|
|
15
|
+
channel
|
|
16
|
+
} = this.props;
|
|
17
|
+
createAnalyticsEvent(createEventPayload('ui.linkPicker.unhandledErrorCaught', {
|
|
18
|
+
browserInfo: ((_window = window) === null || _window === void 0 ? void 0 : (_window$navigator = _window.navigator) === null || _window$navigator === void 0 ? void 0 : _window$navigator.userAgent) || 'unknown',
|
|
19
|
+
error: analyticsErrorPayload.error.toString(),
|
|
20
|
+
componentStack: (_analyticsErrorPayloa = (_analyticsErrorPayloa2 = analyticsErrorPayload.info) === null || _analyticsErrorPayloa2 === void 0 ? void 0 : _analyticsErrorPayloa2.componentStack) !== null && _analyticsErrorPayloa !== void 0 ? _analyticsErrorPayloa : ''
|
|
21
|
+
})).fire(channel);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.state = {
|
|
25
|
+
hasError: false
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentDidCatch(error, info) {
|
|
30
|
+
const {
|
|
31
|
+
onError
|
|
32
|
+
} = this.props;
|
|
33
|
+
const payload = {
|
|
34
|
+
error,
|
|
35
|
+
info
|
|
36
|
+
};
|
|
37
|
+
this.fireAnalytics(payload);
|
|
38
|
+
onError && onError(error, info);
|
|
39
|
+
this.setState({
|
|
40
|
+
hasError: true
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
render() {
|
|
45
|
+
const {
|
|
46
|
+
children,
|
|
47
|
+
ErrorComponent
|
|
48
|
+
} = this.props;
|
|
49
|
+
const {
|
|
50
|
+
hasError
|
|
51
|
+
} = this.state;
|
|
52
|
+
|
|
53
|
+
if (hasError && ErrorComponent) {
|
|
54
|
+
return /*#__PURE__*/React.createElement(ErrorComponent, null);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return children;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
const ErrorBoundary = withAnalyticsEvents()(BaseErrorBoundary);
|
|
62
|
+
export default ErrorBoundary;
|
package/dist/es2019/ui/index.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
|
-
import { AnalyticsErrorBoundary } from '@atlaskit/analytics-next';
|
|
3
2
|
import { LazySuspense, lazyForPaint } from 'react-loosely-lazy';
|
|
3
|
+
import { AnalyticsContext } from '@atlaskit/analytics-next';
|
|
4
4
|
import { COMPONENT_NAME, ANALYTICS_CHANNEL } from '../common/constants';
|
|
5
5
|
import { name as packageName, version as packageVersion } from '../version.json';
|
|
6
|
+
import ErrorBoundary from './error-boundary';
|
|
6
7
|
import { ErrorBoundaryFallback } from './error-boundary-fallback';
|
|
7
8
|
import { LoaderFallback } from './loader-fallback';
|
|
8
9
|
export const PACKAGE_DATA = {
|
|
9
10
|
packageName,
|
|
10
11
|
packageVersion,
|
|
11
|
-
componentName: COMPONENT_NAME
|
|
12
|
+
componentName: COMPONENT_NAME,
|
|
13
|
+
source: COMPONENT_NAME
|
|
12
14
|
};
|
|
13
15
|
const LazyLinkPicker = lazyForPaint(() => import(
|
|
14
16
|
/* webpackChunkName: "@atlaskit-internal_link-picker" */
|
|
15
17
|
'./link-picker'));
|
|
16
|
-
export const ComposedLinkPicker = /*#__PURE__*/memo(props => /*#__PURE__*/React.createElement(
|
|
18
|
+
export const ComposedLinkPicker = /*#__PURE__*/memo(props => /*#__PURE__*/React.createElement(AnalyticsContext, {
|
|
19
|
+
data: PACKAGE_DATA
|
|
20
|
+
}, /*#__PURE__*/React.createElement(ErrorBoundary, {
|
|
17
21
|
channel: ANALYTICS_CHANNEL,
|
|
18
|
-
data: PACKAGE_DATA,
|
|
19
22
|
ErrorComponent: ErrorBoundaryFallback
|
|
20
23
|
}, /*#__PURE__*/React.createElement(LazySuspense, {
|
|
21
24
|
fallback: LoaderFallback
|
|
22
|
-
}, /*#__PURE__*/React.createElement(LazyLinkPicker, props))));
|
|
25
|
+
}, /*#__PURE__*/React.createElement(LazyLinkPicker, props)))));
|
|
@@ -10,13 +10,13 @@ import Tabs, { Tab, TabList } from '@atlaskit/tabs';
|
|
|
10
10
|
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
11
11
|
import createEventPayload from '../../analytics.codegen';
|
|
12
12
|
import { ANALYTICS_CHANNEL } from '../../common/constants';
|
|
13
|
+
import { useLinkPickerAnalytics, withInputFieldTracking, withLinkPickerAnalyticsContext } from '../../common/analytics';
|
|
14
|
+
import { normalizeUrl, isSafeUrl } from '../../common/utils/url';
|
|
13
15
|
import { usePlugins } from '../../services/use-plugins';
|
|
14
16
|
import { useSearchQuery } from '../../services/use-search-query';
|
|
15
|
-
import { withLinkPickerAnalyticsContext } from '../../services/with-link-picker-analytics';
|
|
16
|
-
import { messages } from './messages';
|
|
17
|
-
import PanelTextInput from './text-input';
|
|
18
|
-
import { normalizeUrl, isSafeUrl } from '../../common/utils/url';
|
|
19
17
|
import useFixHeight from '../../controllers/use-fix-height';
|
|
18
|
+
import { messages } from './messages';
|
|
19
|
+
import TextInput from './text-input';
|
|
20
20
|
import { rootContainerStyles, searchIconStyles, tabsWrapperStyles, flexColumnStyles, formFooterMargin } from './styled';
|
|
21
21
|
import { browser } from './browser';
|
|
22
22
|
import Announcer from './announcer';
|
|
@@ -61,6 +61,23 @@ function reducer(state, payload) {
|
|
|
61
61
|
...payload
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Bind input fields to analytics tracking
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const getLinkFieldContent = value => {
|
|
70
|
+
if (!Boolean(value)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return isSafeUrl(value) ? 'url' : 'text_string';
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const LinkInputField = withInputFieldTracking(TextInput, 'link', (event, attributes) => ({ ...attributes,
|
|
78
|
+
linkFieldContent: getLinkFieldContent(event.currentTarget.value)
|
|
79
|
+
}));
|
|
80
|
+
const DisplayTextInputField = withInputFieldTracking(TextInput, 'displayText');
|
|
64
81
|
|
|
65
82
|
function LinkPicker({
|
|
66
83
|
onSubmit,
|
|
@@ -102,6 +119,10 @@ function LinkPicker({
|
|
|
102
119
|
const isEditing = !!initUrl;
|
|
103
120
|
const selectedItem = items === null || items === void 0 ? void 0 : items[selectedIndex];
|
|
104
121
|
const isSelectedItem = (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.url) === url;
|
|
122
|
+
const {
|
|
123
|
+
trackAttribute,
|
|
124
|
+
getAttributes
|
|
125
|
+
} = useLinkPickerAnalytics();
|
|
105
126
|
useEffect(() => {
|
|
106
127
|
// Anything in here is fired on component mount.
|
|
107
128
|
const event = createAnalyticsEvent(createEventPayload('ui.inlineDialog.viewed.linkPicker', {}));
|
|
@@ -117,14 +138,14 @@ function LinkPicker({
|
|
|
117
138
|
onContentResize();
|
|
118
139
|
}
|
|
119
140
|
}, [onContentResize, items, isLoading, isActivePlugin, tabs]);
|
|
120
|
-
const handleChangeUrl = useCallback(
|
|
141
|
+
const handleChangeUrl = useCallback(e => {
|
|
121
142
|
dispatch({
|
|
122
|
-
url
|
|
143
|
+
url: e.currentTarget.value
|
|
123
144
|
});
|
|
124
145
|
}, []);
|
|
125
|
-
const handleChangeText = useCallback(
|
|
146
|
+
const handleChangeText = useCallback(e => {
|
|
126
147
|
dispatch({
|
|
127
|
-
displayText
|
|
148
|
+
displayText: e.currentTarget.value
|
|
128
149
|
});
|
|
129
150
|
}, []);
|
|
130
151
|
const handleKeyDown = useCallback(event => {
|
|
@@ -155,6 +176,11 @@ function LinkPicker({
|
|
|
155
176
|
}
|
|
156
177
|
|
|
157
178
|
if ([KEY_CODE_ARROW_DOWN, KEY_CODE_ARROW_UP].includes(keyCode) && items[updatedIndex]) {
|
|
179
|
+
/**
|
|
180
|
+
* Manually track that the url has been updated using searchResult method
|
|
181
|
+
*/
|
|
182
|
+
trackAttribute('linkFieldContent', getLinkFieldContent(items[updatedIndex].url));
|
|
183
|
+
trackAttribute('linkFieldContentInputMethod', 'searchResult');
|
|
158
184
|
dispatch({
|
|
159
185
|
activeIndex: updatedIndex,
|
|
160
186
|
selectedIndex: updatedIndex,
|
|
@@ -162,7 +188,7 @@ function LinkPicker({
|
|
|
162
188
|
invalidUrl: false
|
|
163
189
|
});
|
|
164
190
|
}
|
|
165
|
-
}, [activeIndex, items]);
|
|
191
|
+
}, [activeIndex, items, trackAttribute]);
|
|
166
192
|
const handleClear = useCallback(field => {
|
|
167
193
|
dispatch({
|
|
168
194
|
[field]: ''
|
|
@@ -187,7 +213,12 @@ function LinkPicker({
|
|
|
187
213
|
const event = createAnalyticsEvent(createEventPayload('ui.form.submitted.linkPicker', {})); // Clone the event so that it can be emitted for consumer usage
|
|
188
214
|
// This must happen BEFORE the original event is fired!
|
|
189
215
|
|
|
190
|
-
const consumerEvent = event.clone(); //
|
|
216
|
+
const consumerEvent = event.clone(); // Cloned event doesnt have the attributes that are added by
|
|
217
|
+
// the analytics listener in the LinkPickerAnalyticsContext, add them here
|
|
218
|
+
|
|
219
|
+
consumerEvent === null || consumerEvent === void 0 ? void 0 : consumerEvent.update({
|
|
220
|
+
attributes: getAttributes()
|
|
221
|
+
}); // Dispatch the original event to our channel
|
|
191
222
|
|
|
192
223
|
event.fire(ANALYTICS_CHANNEL);
|
|
193
224
|
onSubmit({
|
|
@@ -201,7 +232,7 @@ function LinkPicker({
|
|
|
201
232
|
rawUrl: state.url
|
|
202
233
|
} : {})
|
|
203
234
|
}, consumerEvent);
|
|
204
|
-
}, [displayText, onSubmit, state.url, createAnalyticsEvent]);
|
|
235
|
+
}, [displayText, onSubmit, state.url, createAnalyticsEvent, getAttributes]);
|
|
205
236
|
const handleSelected = useCallback(objectId => {
|
|
206
237
|
const selectedItem = items === null || items === void 0 ? void 0 : items.find(item => item.objectId === objectId);
|
|
207
238
|
|
|
@@ -211,9 +242,15 @@ function LinkPicker({
|
|
|
211
242
|
name
|
|
212
243
|
} = selectedItem;
|
|
213
244
|
dispatchEvent(new Event('submit'));
|
|
245
|
+
/**
|
|
246
|
+
* Manually track that the url has been updated using searchResult method
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
trackAttribute('linkFieldContent', getLinkFieldContent(url));
|
|
250
|
+
trackAttribute('linkFieldContentInputMethod', 'searchResult');
|
|
214
251
|
handleInsert(url, name, 'typeAhead');
|
|
215
252
|
}
|
|
216
|
-
}, [handleInsert, items]);
|
|
253
|
+
}, [handleInsert, trackAttribute, items]);
|
|
217
254
|
const handleSubmit = useCallback(event => {
|
|
218
255
|
event.preventDefault();
|
|
219
256
|
|
|
@@ -257,7 +294,7 @@ function LinkPicker({
|
|
|
257
294
|
delay: 250
|
|
258
295
|
}), jsx(VisuallyHidden, {
|
|
259
296
|
id: screenReaderDescriptionId
|
|
260
|
-
}, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(
|
|
297
|
+
}, jsx(FormattedMessage, messages.searchLinkAriaDescription)), jsx(LinkInputField, {
|
|
261
298
|
role: "combobox",
|
|
262
299
|
autoComplete: "off",
|
|
263
300
|
name: "url",
|
|
@@ -277,7 +314,7 @@ function LinkPicker({
|
|
|
277
314
|
onClear: handleClear,
|
|
278
315
|
onKeyDown: handleKeyDown,
|
|
279
316
|
onChange: handleChangeUrl
|
|
280
|
-
}), jsx(
|
|
317
|
+
}), jsx(DisplayTextInputField, {
|
|
281
318
|
autoComplete: "off",
|
|
282
319
|
name: "displayText",
|
|
283
320
|
testId: testIds.textInputField,
|
|
@@ -4,6 +4,8 @@ import _extends from "@babel/runtime/helpers/extends";
|
|
|
4
4
|
import { jsx } from '@emotion/react';
|
|
5
5
|
import { useIntl, FormattedMessage, defineMessages } from 'react-intl-next';
|
|
6
6
|
import Button from '@atlaskit/button';
|
|
7
|
+
import EmptyState from '@atlaskit/empty-state';
|
|
8
|
+
import GenericErrorSVG from '../../../common/generic-error-svg';
|
|
7
9
|
export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
|
|
8
10
|
export const messages = defineMessages({
|
|
9
11
|
searchErrorHeader: {
|
|
@@ -17,8 +19,6 @@ export const messages = defineMessages({
|
|
|
17
19
|
description: 'Describes possible actions when search throws an error'
|
|
18
20
|
}
|
|
19
21
|
});
|
|
20
|
-
import EmptyState from '@atlaskit/empty-state';
|
|
21
|
-
import GenericErrorSVG from '../../../common/generic-error-svg';
|
|
22
22
|
export const testIds = {
|
|
23
23
|
searchError: 'link-search-error'
|
|
24
24
|
};
|
|
@@ -8,23 +8,21 @@ import Tooltip from '@atlaskit/tooltip';
|
|
|
8
8
|
import Textfield from '@atlaskit/textfield';
|
|
9
9
|
import Selectclear from '@atlaskit/icon/glyph/select-clear';
|
|
10
10
|
import { isRedoEvent, isUndoEvent } from '../utils';
|
|
11
|
-
import { clearTextButtonStyles, fieldStyles } from './styled';
|
|
12
11
|
import { testIds } from '../';
|
|
12
|
+
import { clearTextButtonStyles, fieldStyles } from './styled';
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
...restProps
|
|
27
|
-
} = props;
|
|
14
|
+
const TextInput = ({
|
|
15
|
+
name,
|
|
16
|
+
label,
|
|
17
|
+
autoFocus,
|
|
18
|
+
onRedo,
|
|
19
|
+
onUndo,
|
|
20
|
+
onKeyDown,
|
|
21
|
+
onClear,
|
|
22
|
+
clearLabel,
|
|
23
|
+
error,
|
|
24
|
+
...restProps
|
|
25
|
+
}) => {
|
|
28
26
|
const inputRef = useRef(null);
|
|
29
27
|
const handleRef = useCallback(input => {
|
|
30
28
|
if (input) {
|
|
@@ -38,9 +36,6 @@ const PanelTextInput = props => {
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
}, [autoFocus]);
|
|
41
|
-
const handleChange = useCallback(e => {
|
|
42
|
-
onChange(e.currentTarget.value);
|
|
43
|
-
}, [onChange]);
|
|
44
39
|
const handleKeydown = useCallback(e => {
|
|
45
40
|
if (typeof onUndo === 'function' && isUndoEvent(e)) {
|
|
46
41
|
e.preventDefault();
|
|
@@ -83,7 +78,6 @@ const PanelTextInput = props => {
|
|
|
83
78
|
}) => {
|
|
84
79
|
return jsx(Fragment, null, jsx(Textfield, _extends({}, fieldProps, restProps, {
|
|
85
80
|
onKeyDown: handleKeydown,
|
|
86
|
-
onChange: handleChange,
|
|
87
81
|
ref: handleRef,
|
|
88
82
|
elemAfterInput: clearText,
|
|
89
83
|
isInvalid: !!error
|
|
@@ -93,4 +87,4 @@ const PanelTextInput = props => {
|
|
|
93
87
|
}));
|
|
94
88
|
};
|
|
95
89
|
|
|
96
|
-
export default
|
|
90
|
+
export default TextInput;
|
package/dist/es2019/version.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates analytics utilities from the package analytics spec yaml
|
|
5
5
|
*
|
|
6
|
-
* @codegen <<SignedSource::
|
|
6
|
+
* @codegen <<SignedSource::3c46f1d8f463b6898a7a0e1e1f4676b4>>
|
|
7
7
|
* @codegenCommand yarn workspace @atlaskit/link-picker run codegen-analytics
|
|
8
8
|
*/
|
|
9
9
|
function createEventPayload(eventKey, attributes) {
|