cozy-sharing 5.0.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/dist/SharedRecipients.js +1 -1
- package/dist/SharedStatus.js +1 -1
- package/dist/SharingBanner/hooks/useSharingInfos.js +1 -1
- package/dist/SharingDetailsModal.js +1 -1
- package/dist/SharingOwnerAvatar.js +1 -1
- package/dist/SharingProvider.js +21 -8
- package/dist/components/ConfirmTrustedRecipientsDialog.js +1 -1
- package/dist/components/ContactSuggestion.js +15 -27
- package/dist/components/EditLinkPermissionDialog.js +72 -0
- package/dist/components/EditLinkPermissionDialog.spec.js +48 -0
- package/dist/components/EditableSharingModal.js +15 -62
- package/dist/components/{AvatarPlusX.js → Recipient/AvatarPlusX.js} +1 -1
- package/dist/components/{Identity.js → Recipient/Identity.js} +17 -16
- package/dist/components/Recipient/LinkRecipient.js +72 -0
- package/dist/components/Recipient/LinkRecipientPermissions.js +159 -0
- package/dist/components/Recipient/OwnerIdentity.js +44 -0
- package/dist/components/Recipient/Recipient.js +72 -0
- package/dist/components/{Recipient.spec.js → Recipient/Recipient.spec.js} +4 -45
- package/dist/components/Recipient/RecipientAvatar.js +33 -0
- package/dist/components/Recipient/RecipientConfirm.js +63 -0
- package/dist/components/Recipient/RecipientPermissions.js +126 -0
- package/dist/components/Recipient/RecipientPlusX.js +45 -0
- package/dist/components/Recipient/RecipientStatus.js +64 -0
- package/dist/components/Recipient/RecipientWithoutStatus.js +45 -0
- package/dist/components/Recipient/RecipientsAvatars.js +100 -0
- package/dist/components/{RecipientsAvatars.spec.js → Recipient/RecipientsAvatars.spec.js} +43 -2
- package/dist/components/{recipient.styl → Recipient/recipient.styl} +6 -2
- package/dist/components/ShareAutosuggest.js +176 -252
- package/dist/components/ShareAutosuggest.spec.js +3 -2
- package/dist/components/ShareByEmail.js +142 -193
- package/dist/components/ShareByEmail.spec.js +42 -23
- package/dist/components/ShareByLink.js +251 -357
- package/dist/components/ShareDialogCozyToCozy.js +16 -4
- package/dist/components/ShareDialogCozyToCozy.spec.js +12 -1
- package/dist/components/ShareDialogOnlyByLink.js +23 -10
- package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +7 -7
- package/dist/components/ShareModal.js +5 -1
- package/dist/components/ShareRecipientsInput.js +44 -128
- package/dist/components/WhoHasAccess.js +43 -5
- package/dist/components/WhoHasAccessLight.js +3 -19
- package/dist/components/autosuggest.styl +21 -36
- package/dist/helpers/documentType.js +6 -0
- package/dist/helpers/hooks.js +8 -1
- package/dist/helpers/link.js +5 -0
- package/dist/helpers/permissions.js +8 -0
- package/dist/helpers/recipients.js +24 -0
- package/dist/helpers/sharings.js +72 -53
- package/dist/helpers/synchronousJobQueue.js +67 -0
- package/dist/helpers/synchronousJobQueue.spec.js +97 -0
- package/dist/index.js +2 -1
- package/dist/queries/queries.js +72 -0
- package/dist/share.styl +0 -15
- package/dist/stylesheet.css +67 -92
- package/locales/en.json +29 -41
- package/locales/fr.json +26 -39
- package/package.json +10 -10
- package/src/SharedRecipients.jsx +1 -1
- package/src/SharedStatus.jsx +1 -1
- package/src/SharingBanner/hooks/useSharingInfos.jsx +1 -1
- package/src/SharingDetailsModal.jsx +1 -1
- package/src/SharingOwnerAvatar.jsx +1 -1
- package/src/SharingProvider.jsx +17 -4
- package/src/components/ConfirmTrustedRecipientsDialog.jsx +1 -1
- package/src/components/ContactSuggestion.jsx +25 -10
- package/src/components/EditLinkPermissionDialog.jsx +89 -0
- package/src/components/EditLinkPermissionDialog.spec.jsx +52 -0
- package/src/components/EditableSharingModal.jsx +58 -100
- package/src/components/{AvatarPlusX.jsx → Recipient/AvatarPlusX.jsx} +1 -1
- package/src/components/{Identity.jsx → Recipient/Identity.jsx} +0 -0
- package/src/components/Recipient/LinkRecipient.jsx +59 -0
- package/src/components/Recipient/LinkRecipientPermissions.jsx +157 -0
- package/src/components/Recipient/OwnerIdentity.jsx +20 -0
- package/src/components/Recipient/Recipient.jsx +79 -0
- package/src/components/{Recipient.spec.jsx → Recipient/Recipient.spec.jsx} +4 -42
- package/src/components/Recipient/RecipientAvatar.jsx +38 -0
- package/src/components/Recipient/RecipientConfirm.jsx +37 -0
- package/src/components/Recipient/RecipientPermissions.jsx +114 -0
- package/src/components/Recipient/RecipientPlusX.jsx +28 -0
- package/src/components/Recipient/RecipientStatus.jsx +48 -0
- package/src/components/Recipient/RecipientWithoutStatus.jsx +21 -0
- package/src/components/Recipient/RecipientsAvatars.jsx +108 -0
- package/src/components/{RecipientsAvatars.spec.jsx → Recipient/RecipientsAvatars.spec.jsx} +43 -2
- package/src/components/{recipient.styl → Recipient/recipient.styl} +6 -2
- package/src/components/ShareAutosuggest.jsx +136 -127
- package/src/components/ShareAutosuggest.spec.jsx +12 -8
- package/src/components/ShareByEmail.jsx +65 -90
- package/src/components/ShareByEmail.spec.jsx +39 -11
- package/src/components/ShareByLink.jsx +146 -238
- package/src/components/ShareDialogCozyToCozy.jsx +14 -2
- package/src/components/ShareDialogCozyToCozy.spec.jsx +8 -1
- package/src/components/ShareDialogOnlyByLink.jsx +30 -13
- package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +5 -5
- package/src/components/ShareModal.jsx +5 -1
- package/src/components/ShareRecipientsInput.jsx +30 -44
- package/src/components/WhoHasAccess.jsx +85 -27
- package/src/components/WhoHasAccessLight.jsx +2 -1
- package/src/components/__snapshots__/ContactSuggestion.spec.jsx.snap +915 -132
- package/src/components/__snapshots__/ShareRecipientsInput.spec.jsx.snap +86 -102
- package/src/components/autosuggest.styl +21 -36
- package/src/helpers/documentType.js +6 -0
- package/src/helpers/hooks.js +11 -1
- package/src/helpers/link.js +4 -0
- package/src/helpers/permissions.js +15 -0
- package/src/helpers/recipients.js +21 -0
- package/src/helpers/sharings.js +15 -12
- package/src/helpers/synchronousJobQueue.js +22 -0
- package/src/helpers/synchronousJobQueue.spec.js +75 -0
- package/src/index.jsx +1 -0
- package/src/queries/queries.js +75 -0
- package/src/share.styl +0 -15
- package/dist/SharingBanner/helpers/queries.js +0 -12
- package/dist/components/ContactsAndGroupsDataLoader.js +0 -50
- package/dist/components/Recipient.js +0 -450
- package/src/SharingBanner/helpers/queries.js +0 -14
- package/src/components/ContactsAndGroupsDataLoader.jsx +0 -30
- package/src/components/Recipient.jsx +0 -415
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import cx from 'classnames'
|
|
3
|
+
import { useClient } from 'cozy-client'
|
|
4
|
+
|
|
5
|
+
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
6
|
+
import LinkIcon from 'cozy-ui/transpiled/react/Icons/Link'
|
|
7
|
+
|
|
8
|
+
import RecipientAvatar from './RecipientAvatar'
|
|
9
|
+
import AvatarPlusX from './AvatarPlusX'
|
|
10
|
+
import styles from './recipient.styl'
|
|
11
|
+
import { getDisplayName } from '../../models'
|
|
12
|
+
|
|
13
|
+
export const MAX_DISPLAYED_RECIPIENTS = 3
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Exclude me from the list of recipients.
|
|
17
|
+
* @typedef {object} Recipient
|
|
18
|
+
* @param {array<Recipient>} recipients - List of recipients
|
|
19
|
+
* @param {boolean} isOwner - Indicates if I'm the owner or not
|
|
20
|
+
* @param {object} client - CozyClient instance
|
|
21
|
+
* @returns {array<Recipient>} List of recipients without me if I'm the owner
|
|
22
|
+
*/
|
|
23
|
+
export const excludeMeAsOwnerFromRecipients = ({
|
|
24
|
+
recipients,
|
|
25
|
+
isOwner,
|
|
26
|
+
client
|
|
27
|
+
}) => {
|
|
28
|
+
return recipients.filter(recipient => {
|
|
29
|
+
if (isOwner) {
|
|
30
|
+
return recipient.status !== 'owner'
|
|
31
|
+
}
|
|
32
|
+
return recipient.instance !== client.options.uri
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const RecipientsAvatars = ({
|
|
37
|
+
recipients,
|
|
38
|
+
link,
|
|
39
|
+
size,
|
|
40
|
+
className,
|
|
41
|
+
onClick,
|
|
42
|
+
isOwner,
|
|
43
|
+
showMeAsOwner
|
|
44
|
+
}) => {
|
|
45
|
+
const client = useClient()
|
|
46
|
+
const filteredRecipients = showMeAsOwner
|
|
47
|
+
? recipients.slice().reverse() // we slice first to clone the original array because reverser() mutates it
|
|
48
|
+
: excludeMeAsOwnerFromRecipients({
|
|
49
|
+
recipients,
|
|
50
|
+
isOwner,
|
|
51
|
+
client
|
|
52
|
+
}).reverse()
|
|
53
|
+
// we reverse the recipients array because we use `flex-direction: row-reverse` to display them correctly
|
|
54
|
+
|
|
55
|
+
const isAvatarPlusX = filteredRecipients.length > MAX_DISPLAYED_RECIPIENTS
|
|
56
|
+
const extraRecipients = filteredRecipients
|
|
57
|
+
.slice(MAX_DISPLAYED_RECIPIENTS)
|
|
58
|
+
.map(recipient => getDisplayName(recipient))
|
|
59
|
+
const shownRecipients = filteredRecipients.slice(0, MAX_DISPLAYED_RECIPIENTS)
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
className={cx(
|
|
64
|
+
styles['recipients-avatars'],
|
|
65
|
+
{
|
|
66
|
+
[styles['--interactive']]: onClick
|
|
67
|
+
},
|
|
68
|
+
className
|
|
69
|
+
)}
|
|
70
|
+
onClick={onClick}
|
|
71
|
+
>
|
|
72
|
+
{link && (
|
|
73
|
+
<span data-testid="recipientsAvatars-link">
|
|
74
|
+
<Avatar
|
|
75
|
+
className={styles['recipients-avatars--link']}
|
|
76
|
+
icon={LinkIcon}
|
|
77
|
+
size={size}
|
|
78
|
+
/>
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
{isAvatarPlusX && (
|
|
82
|
+
<span data-testid="recipientsAvatars-plusX">
|
|
83
|
+
<AvatarPlusX
|
|
84
|
+
className={styles['recipients-avatars--plusX']}
|
|
85
|
+
extraRecipients={extraRecipients}
|
|
86
|
+
size={size}
|
|
87
|
+
/>
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
90
|
+
{shownRecipients.map((recipient, idx) => (
|
|
91
|
+
<span
|
|
92
|
+
data-testid={`recipientsAvatars-avatar${
|
|
93
|
+
recipient.status === 'owner' ? '-owner' : ''
|
|
94
|
+
}`}
|
|
95
|
+
key={idx}
|
|
96
|
+
>
|
|
97
|
+
<RecipientAvatar
|
|
98
|
+
recipient={recipient}
|
|
99
|
+
size={size}
|
|
100
|
+
className={cx(styles['recipients-avatars--avatar'])}
|
|
101
|
+
/>
|
|
102
|
+
</span>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default RecipientsAvatars
|
|
@@ -2,8 +2,11 @@ import React from 'react'
|
|
|
2
2
|
import { createMockClient } from 'cozy-client'
|
|
3
3
|
import { render } from '@testing-library/react'
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import RecipientsAvatars, {
|
|
6
|
+
MAX_DISPLAYED_RECIPIENTS,
|
|
7
|
+
excludeMeAsOwnerFromRecipients
|
|
8
|
+
} from './RecipientsAvatars'
|
|
9
|
+
import AppLike from '../../../test/AppLike'
|
|
7
10
|
|
|
8
11
|
const mockRecipients = new Array(MAX_DISPLAYED_RECIPIENTS - 1)
|
|
9
12
|
.fill(
|
|
@@ -113,3 +116,41 @@ describe('RecipientsAvatars', () => {
|
|
|
113
116
|
expect(getByTestId('recipientsAvatars-link')).toBeTruthy()
|
|
114
117
|
})
|
|
115
118
|
})
|
|
119
|
+
|
|
120
|
+
describe('excludeMeAsOwnerFromRecipients', () => {
|
|
121
|
+
test('excludeMeAsOwnerFromRecipients behavior', () => {
|
|
122
|
+
const recipients = [
|
|
123
|
+
{
|
|
124
|
+
status: 'owner',
|
|
125
|
+
instance: 'http://foo1.cozy.bar'
|
|
126
|
+
},
|
|
127
|
+
{ status: 'pending', instance: 'http://foo2.cozy.bar' },
|
|
128
|
+
{
|
|
129
|
+
status: 'pending',
|
|
130
|
+
instance: 'http://foo3.cozy.bar'
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
const client = { options: { uri: 'http://foo2.cozy.bar' } }
|
|
134
|
+
expect(
|
|
135
|
+
excludeMeAsOwnerFromRecipients({ recipients, isOwner: true, client })
|
|
136
|
+
).toEqual([
|
|
137
|
+
{ status: 'pending', instance: 'http://foo2.cozy.bar' },
|
|
138
|
+
{
|
|
139
|
+
status: 'pending',
|
|
140
|
+
instance: 'http://foo3.cozy.bar'
|
|
141
|
+
}
|
|
142
|
+
])
|
|
143
|
+
expect(
|
|
144
|
+
excludeMeAsOwnerFromRecipients({ recipients, isOwner: false, client })
|
|
145
|
+
).toEqual([
|
|
146
|
+
{
|
|
147
|
+
status: 'owner',
|
|
148
|
+
instance: 'http://foo1.cozy.bar'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
status: 'pending',
|
|
152
|
+
instance: 'http://foo3.cozy.bar'
|
|
153
|
+
}
|
|
154
|
+
])
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
.recipient-user
|
|
53
53
|
font-size 1.125rem
|
|
54
54
|
|
|
55
|
+
.link-recipient-icon-circle
|
|
56
|
+
border 1px solid var(--borderMainColor)
|
|
57
|
+
background-color unset
|
|
58
|
+
color unset
|
|
59
|
+
|
|
55
60
|
.recipients-avatars
|
|
56
61
|
display inline-flex
|
|
57
62
|
flex-direction row-reverse
|
|
@@ -74,5 +79,4 @@
|
|
|
74
79
|
|
|
75
80
|
&--plusX,
|
|
76
81
|
&--avatar
|
|
77
|
-
margin-right -0.6rem
|
|
78
|
-
|
|
82
|
+
margin-right -0.6rem
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState, useRef } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import Autosuggest from 'react-autosuggest'
|
|
4
4
|
|
|
5
5
|
import { Spinner } from 'cozy-ui/transpiled/react/Spinner'
|
|
6
6
|
import palette from 'cozy-ui/transpiled/react/palette'
|
|
7
|
+
import Chip from 'cozy-ui/transpiled/react/Chips'
|
|
8
|
+
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
7
9
|
|
|
8
10
|
import styles from './autosuggest.styl'
|
|
9
|
-
import BoldCross from '../../assets/icons/icon-cross-bold.svg'
|
|
10
11
|
|
|
11
|
-
import { getDisplayName, Contact } from '../models'
|
|
12
|
+
import { getDisplayName, getInitials, Contact } from '../models'
|
|
12
13
|
import {
|
|
13
14
|
cozyUrlMatch,
|
|
14
15
|
emailMatch,
|
|
@@ -18,24 +19,22 @@ import {
|
|
|
18
19
|
import ContactSuggestion from './ContactSuggestion'
|
|
19
20
|
import { extractEmails, validateEmail } from '../helpers/email'
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
computeSuggestions(value) {
|
|
38
|
-
const { contactsAndGroups } = this.props
|
|
22
|
+
const ShareAutocomplete = ({
|
|
23
|
+
loading,
|
|
24
|
+
contactsAndGroups,
|
|
25
|
+
recipients,
|
|
26
|
+
onFocus,
|
|
27
|
+
onPick,
|
|
28
|
+
onRemove,
|
|
29
|
+
placeholder
|
|
30
|
+
}) => {
|
|
31
|
+
const [inputValue, setInputValue] = useState('')
|
|
32
|
+
const [suggestions, setSuggestions] = useState([])
|
|
33
|
+
const [isPasted, setIsPasted] = useState(false)
|
|
34
|
+
|
|
35
|
+
const autosuggestRef = useRef(null)
|
|
36
|
+
|
|
37
|
+
const computeSuggestions = value => {
|
|
39
38
|
const inputValue = value.trim().toLowerCase()
|
|
40
39
|
|
|
41
40
|
return inputValue.length === 0
|
|
@@ -49,154 +48,164 @@ export default class ShareAutocomplete extends Component {
|
|
|
49
48
|
)
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
onSuggestionsFetchRequested = ({ value }) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
const onSuggestionsFetchRequested = ({ value }) => {
|
|
52
|
+
const computedSuggestions = computeSuggestions(value)
|
|
53
|
+
|
|
54
|
+
const newSuggestions =
|
|
55
|
+
computedSuggestions.length === 0 && validateEmail(value)
|
|
56
|
+
? [
|
|
57
|
+
{
|
|
58
|
+
email: value,
|
|
59
|
+
_type: Contact.doctype
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
: computedSuggestions
|
|
63
|
+
|
|
64
|
+
setSuggestions(newSuggestions)
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
onSuggestionsClearRequested = () => {
|
|
60
|
-
|
|
67
|
+
const onSuggestionsClearRequested = () => {
|
|
68
|
+
setSuggestions([])
|
|
61
69
|
}
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
|
|
71
|
+
const onAutosuggestPaste = () => {
|
|
72
|
+
setIsPasted(true)
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
if (
|
|
75
|
+
const onAutosuggestChange = (event, { newValue, method }) => {
|
|
76
|
+
if (isPasted) {
|
|
68
77
|
const emails = extractEmails(newValue)
|
|
69
78
|
if (emails) {
|
|
70
79
|
emails.map(email => {
|
|
71
|
-
|
|
80
|
+
onAutosuggestPick({ email })
|
|
72
81
|
})
|
|
73
82
|
}
|
|
74
|
-
|
|
83
|
+
setIsPasted(false)
|
|
75
84
|
} else {
|
|
76
85
|
if (typeof newValue !== 'object') {
|
|
77
|
-
|
|
86
|
+
setInputValue(newValue)
|
|
78
87
|
} else if (method === 'click' || method === 'enter') {
|
|
79
88
|
// A suggestion has been picked
|
|
80
|
-
|
|
89
|
+
onAutosuggestPick(newValue)
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
onKeyPress = event => {
|
|
94
|
+
const onKeyPress = event => {
|
|
86
95
|
// The user wants to add an unknown email
|
|
87
96
|
if (
|
|
88
97
|
((event.key === 'Enter' || event.keyCode === 13) &&
|
|
89
|
-
validateEmail(
|
|
98
|
+
validateEmail(inputValue)) ||
|
|
90
99
|
((event.key === 'Space' || event.keyCode === 32) &&
|
|
91
|
-
validateEmail(
|
|
100
|
+
validateEmail(inputValue))
|
|
92
101
|
) {
|
|
93
|
-
|
|
102
|
+
onAutosuggestPick({ email: inputValue })
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} else if (
|
|
106
|
-
this.state.inputValue !== '' &&
|
|
107
|
-
this.state.inputValue.match(/\S+@\S+/)
|
|
106
|
+
const onKeyUp = ({ key, keyCode }) => {
|
|
107
|
+
if (
|
|
108
|
+
recipients.length > 0 &&
|
|
109
|
+
inputValue === '' &&
|
|
110
|
+
(key === 'Backspace' ||
|
|
111
|
+
keyCode === 8 ||
|
|
112
|
+
key === 'Delete' ||
|
|
113
|
+
keyCode === 46)
|
|
108
114
|
) {
|
|
109
|
-
|
|
110
|
-
this.setState(state => ({ ...state, inputValue: '' }))
|
|
115
|
+
document.getElementById(`recipient_${recipients.length - 1}`).focus()
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.setState(state => ({ ...state, inputValue: '' }))
|
|
117
|
-
requestAnimationFrame(() => this.input && this.input.focus(), 1) // don't ask...
|
|
119
|
+
const onAutosuggestFocus = () => {
|
|
120
|
+
onFocus()
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
const onAutosuggestBlur = () => {
|
|
124
|
+
if (inputValue !== '' && inputValue.match(/\S+@\S+/)) {
|
|
125
|
+
onAutosuggestPick({ email: inputValue })
|
|
126
|
+
}
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{recipients.map((recipient, idx) => {
|
|
130
|
-
const value = getDisplayName(recipient)
|
|
131
|
-
return (
|
|
132
|
-
<div
|
|
133
|
-
className={styles['recipientChip']}
|
|
134
|
-
key={`key_recipient_${idx}`}
|
|
135
|
-
>
|
|
136
|
-
<span>{value}</span>
|
|
137
|
-
<button
|
|
138
|
-
className={styles['removeRecipient']}
|
|
139
|
-
onClick={() => this.onRemove(recipient)}
|
|
140
|
-
>
|
|
141
|
-
<BoldCross />
|
|
142
|
-
</button>
|
|
143
|
-
</div>
|
|
144
|
-
)
|
|
145
|
-
})}
|
|
146
|
-
<input {...inputProps} onKeyPress={this.onKeyPress} />
|
|
147
|
-
{loading && (
|
|
148
|
-
<Spinner
|
|
149
|
-
color={palette.dodgerBlue}
|
|
150
|
-
className="u-flex u-flex-items-center"
|
|
151
|
-
/>
|
|
152
|
-
)}
|
|
153
|
-
</div>
|
|
154
|
-
)
|
|
129
|
+
const onAutosuggestPick = value => {
|
|
130
|
+
onPick(value)
|
|
131
|
+
setInputValue('')
|
|
132
|
+
autosuggestRef?.current?.input?.focus()
|
|
155
133
|
}
|
|
156
134
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<Autosuggest
|
|
162
|
-
ref={self => {
|
|
163
|
-
this.input = self ? self.input : null
|
|
164
|
-
}}
|
|
165
|
-
theme={styles}
|
|
166
|
-
suggestions={suggestions.slice(0, 20)}
|
|
167
|
-
getSuggestionValue={contact => contact}
|
|
168
|
-
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
|
169
|
-
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
|
170
|
-
renderSuggestion={contactOrGroup => (
|
|
171
|
-
<ContactSuggestion
|
|
172
|
-
contacts={contactsAndGroups.filter(
|
|
173
|
-
item => item._type === Contact.doctype
|
|
174
|
-
)}
|
|
175
|
-
contactOrGroup={contactOrGroup}
|
|
176
|
-
/>
|
|
177
|
-
)}
|
|
178
|
-
renderInputComponent={props => this.renderInput(props)}
|
|
179
|
-
highlightFirstSuggestion
|
|
180
|
-
inputProps={{
|
|
181
|
-
onFocus: this.onFocus,
|
|
182
|
-
onChange: this.onChange,
|
|
183
|
-
onPaste: this.onPaste,
|
|
184
|
-
onBlur: this.onBlur,
|
|
185
|
-
value: inputValue,
|
|
186
|
-
type: 'email',
|
|
187
|
-
placeholder
|
|
188
|
-
}}
|
|
189
|
-
/>
|
|
190
|
-
)
|
|
135
|
+
const onAutosuggestRemove = value => {
|
|
136
|
+
onRemove(value)
|
|
137
|
+
autosuggestRef?.current?.input?.focus()
|
|
191
138
|
}
|
|
139
|
+
|
|
140
|
+
const renderInput = inputProps => (
|
|
141
|
+
<div className={styles['recipientsContainer']}>
|
|
142
|
+
{recipients.map((recipient, idx) => {
|
|
143
|
+
const avatarText = getInitials(recipient)
|
|
144
|
+
const name = getDisplayName(recipient)
|
|
145
|
+
return (
|
|
146
|
+
<Chip
|
|
147
|
+
id={`recipient_${idx}`}
|
|
148
|
+
key={`key_recipient_${idx}`}
|
|
149
|
+
avatar={<Avatar text={avatarText} size="xsmall" />}
|
|
150
|
+
label={name}
|
|
151
|
+
onDelete={() => {
|
|
152
|
+
onAutosuggestRemove(recipient)
|
|
153
|
+
}}
|
|
154
|
+
className={styles['recipientChip']}
|
|
155
|
+
/>
|
|
156
|
+
)
|
|
157
|
+
})}
|
|
158
|
+
<input {...inputProps} onKeyPress={onKeyPress} onKeyUp={onKeyUp} />
|
|
159
|
+
{loading && (
|
|
160
|
+
<Spinner
|
|
161
|
+
color={palette.dodgerBlue}
|
|
162
|
+
className="u-flex u-flex-items-center"
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const renderSuggestion = contactOrGroup => (
|
|
169
|
+
<ContactSuggestion
|
|
170
|
+
contacts={contactsAndGroups.filter(
|
|
171
|
+
item => item._type === Contact.doctype
|
|
172
|
+
)}
|
|
173
|
+
contactOrGroup={contactOrGroup}
|
|
174
|
+
/>
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<Autosuggest
|
|
179
|
+
ref={autosuggestRef}
|
|
180
|
+
theme={styles}
|
|
181
|
+
suggestions={suggestions.slice(0, 20)}
|
|
182
|
+
getSuggestionValue={contact => contact}
|
|
183
|
+
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
|
|
184
|
+
onSuggestionsClearRequested={onSuggestionsClearRequested}
|
|
185
|
+
renderSuggestion={renderSuggestion}
|
|
186
|
+
renderInputComponent={renderInput}
|
|
187
|
+
highlightFirstSuggestion
|
|
188
|
+
inputProps={{
|
|
189
|
+
onFocus: onAutosuggestFocus,
|
|
190
|
+
onChange: onAutosuggestChange,
|
|
191
|
+
onPaste: onAutosuggestPaste,
|
|
192
|
+
onBlur: onAutosuggestBlur,
|
|
193
|
+
value: inputValue,
|
|
194
|
+
type: 'email',
|
|
195
|
+
placeholder
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
)
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
ShareAutocomplete.propTypes = {
|
|
202
|
+
loading: PropTypes.bool,
|
|
195
203
|
contactsAndGroups: PropTypes.array,
|
|
196
|
-
|
|
204
|
+
recipients: PropTypes.array.isRequired,
|
|
197
205
|
onFocus: PropTypes.func.isRequired,
|
|
198
206
|
onPick: PropTypes.func.isRequired,
|
|
199
207
|
onRemove: PropTypes.func.isRequired,
|
|
200
|
-
|
|
201
|
-
recipients: PropTypes.array.isRequired
|
|
208
|
+
placeholder: PropTypes.string
|
|
202
209
|
}
|
|
210
|
+
|
|
211
|
+
export default ShareAutocomplete
|
|
@@ -2,20 +2,24 @@ import React from 'react'
|
|
|
2
2
|
import { render, fireEvent } from '@testing-library/react'
|
|
3
3
|
|
|
4
4
|
import ShareAutosuggest from './ShareAutosuggest'
|
|
5
|
+
import AppLike from '../../test/AppLike'
|
|
6
|
+
|
|
5
7
|
describe('ShareAutosuggest', () => {
|
|
6
8
|
it('tests if ShareAutosuggest calls onFocus and onPick', () => {
|
|
7
9
|
const onPick = jest.fn()
|
|
8
10
|
const onFocus = jest.fn()
|
|
9
11
|
const onRemove = jest.fn()
|
|
10
12
|
const { getByPlaceholderText } = render(
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
<AppLike>
|
|
14
|
+
<ShareAutosuggest
|
|
15
|
+
contactsAndGroups={[]}
|
|
16
|
+
placeholder="myPlaceHolder"
|
|
17
|
+
onPick={onPick}
|
|
18
|
+
onFocus={onFocus}
|
|
19
|
+
recipients={[]}
|
|
20
|
+
onRemove={onRemove}
|
|
21
|
+
/>
|
|
22
|
+
</AppLike>
|
|
19
23
|
)
|
|
20
24
|
const inputNode = getByPlaceholderText('myPlaceHolder')
|
|
21
25
|
inputNode.focus()
|