cozy-sharing 17.0.0 → 19.0.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.
- package/CHANGELOG.md +49 -0
- package/dist/SharingProvider.spec.js +84 -80
- package/dist/components/Recipient/LinkRecipientPermissions.js +18 -129
- package/dist/components/Recipient/actions/editPermissionLink.js +71 -0
- package/dist/components/Recipient/actions/readOnlyPermissionLink.js +63 -0
- package/dist/components/ShareByLink.js +45 -185
- package/dist/components/ShareDialogCozyToCozy.js +2 -6
- package/dist/components/ShareDialogOnlyByLink.js +2 -8
- package/dist/components/ShareDialogTwoStepsConfirmationContainer.js +1 -3
- package/dist/components/ShareModal/EditableSharingModal.js +1 -7
- package/dist/components/ShareModal/EditableSharingModal.spec.js +49 -52
- package/dist/components/ShareModal/SharingDetailsModal.js +1 -1
- package/dist/components/ShareModal.js +0 -12
- package/dist/components/ShareRestrictionModal/BoxDate.js +77 -0
- package/dist/components/ShareRestrictionModal/BoxEditingRights.js +83 -0
- package/dist/components/ShareRestrictionModal/BoxPassword.js +138 -0
- package/dist/components/ShareRestrictionModal/ShareRestrictionContentModal.js +94 -0
- package/dist/components/ShareRestrictionModal/ShareRestrictionModal.js +260 -0
- package/dist/components/ShareRestrictionModal/helpers.js +275 -0
- package/dist/components/ShareRestrictionModal/helpers.spec.js +110 -0
- package/dist/components/SharedStatus.spec.js +20 -21
- package/dist/components/WhoHasAccess.js +1 -5
- package/jest.config.js +8 -3
- package/jestHelpers/setup.js +1 -8
- package/locales/en.json +28 -0
- package/locales/fr.json +29 -0
- package/package.json +13 -11
- package/src/SharingProvider.spec.jsx +88 -88
- package/src/components/Recipient/LinkRecipientPermissions.jsx +28 -137
- package/src/components/Recipient/actions/editPermissionLink.js +63 -0
- package/src/components/Recipient/actions/readOnlyPermissionLink.js +59 -0
- package/src/components/ShareByLink.jsx +20 -96
- package/src/components/ShareDialogCozyToCozy.jsx +1 -5
- package/src/components/ShareDialogOnlyByLink.jsx +0 -6
- package/src/components/ShareDialogTwoStepsConfirmationContainer.jsx +0 -2
- package/src/components/ShareModal/EditableSharingModal.jsx +1 -7
- package/src/components/ShareModal/EditableSharingModal.spec.jsx +50 -44
- package/src/components/ShareModal/SharingDetailsModal.jsx +1 -1
- package/src/components/ShareModal.jsx +0 -12
- package/src/components/ShareRestrictionModal/BoxDate.jsx +76 -0
- package/src/components/ShareRestrictionModal/BoxEditingRights.jsx +91 -0
- package/src/components/ShareRestrictionModal/BoxPassword.jsx +112 -0
- package/src/components/ShareRestrictionModal/ShareRestrictionContentModal.jsx +102 -0
- package/src/components/ShareRestrictionModal/ShareRestrictionModal.jsx +177 -0
- package/src/components/ShareRestrictionModal/helpers.js +193 -0
- package/src/components/ShareRestrictionModal/helpers.spec.js +78 -0
- package/src/components/SharedStatus.spec.js +32 -31
- package/src/components/WhoHasAccess.jsx +1 -5
- package/src/components/__snapshots__/SharedStatus.spec.js.snap +59 -129
- package/dist/RefreshableSharings.spec.js +0 -40
- package/dist/components/EditLinkPermissionDialog.js +0 -72
- package/dist/components/EditLinkPermissionDialog.spec.js +0 -48
- package/src/RefreshableSharings.spec.jsx +0 -43
- package/src/components/EditLinkPermissionDialog.jsx +0 -89
- package/src/components/EditLinkPermissionDialog.spec.jsx +0 -53
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import Box from 'cozy-ui/transpiled/react/Box'
|
|
5
|
+
import Icon from 'cozy-ui/transpiled/react/Icon'
|
|
6
|
+
import IconButton from 'cozy-ui/transpiled/react/IconButton'
|
|
7
|
+
import List from 'cozy-ui/transpiled/react/List'
|
|
8
|
+
import ListItem from 'cozy-ui/transpiled/react/ListItem'
|
|
9
|
+
import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
|
|
10
|
+
import ListItemSecondaryAction from 'cozy-ui/transpiled/react/ListItemSecondaryAction'
|
|
11
|
+
import ListItemText from 'cozy-ui/transpiled/react/ListItemText'
|
|
12
|
+
import Switch from 'cozy-ui/transpiled/react/Switch'
|
|
13
|
+
import TextField from 'cozy-ui/transpiled/react/TextField'
|
|
14
|
+
import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
|
|
15
|
+
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
|
|
16
|
+
|
|
17
|
+
import { copyToClipboard } from './helpers'
|
|
18
|
+
|
|
19
|
+
export const BoxPassword = ({
|
|
20
|
+
onChange,
|
|
21
|
+
password,
|
|
22
|
+
onToggle,
|
|
23
|
+
toggle,
|
|
24
|
+
helperText,
|
|
25
|
+
inputProps
|
|
26
|
+
}) => {
|
|
27
|
+
const { t } = useI18n()
|
|
28
|
+
const { showAlert } = useAlert()
|
|
29
|
+
const [displayHelper, setDisplayHelper] = useState(false)
|
|
30
|
+
const inputRef = React.useRef()
|
|
31
|
+
|
|
32
|
+
const handlePasswordToggle = val => {
|
|
33
|
+
const value = val?.target?.checked ?? val
|
|
34
|
+
onToggle(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleChange = val => {
|
|
38
|
+
const value = val?.target?.value ?? val
|
|
39
|
+
onChange(value)
|
|
40
|
+
}
|
|
41
|
+
const handleOnFocus = () => {
|
|
42
|
+
setDisplayHelper(false)
|
|
43
|
+
}
|
|
44
|
+
const handleOnBlur = () => {
|
|
45
|
+
handleChange(inputRef.current.value.trim())
|
|
46
|
+
setDisplayHelper(true)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleCopy = async () => {
|
|
50
|
+
await copyToClipboard(password, { t, showAlert })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Box borderRadius="0.5rem" border="1px solid var(--borderMainColor)">
|
|
55
|
+
<List className="u-p-0">
|
|
56
|
+
<ListItem
|
|
57
|
+
button
|
|
58
|
+
size="large"
|
|
59
|
+
ellipsis={false}
|
|
60
|
+
onClick={() => handlePasswordToggle(!toggle)}
|
|
61
|
+
>
|
|
62
|
+
<ListItemIcon>
|
|
63
|
+
<Icon icon="password" />
|
|
64
|
+
</ListItemIcon>
|
|
65
|
+
<ListItemText primary={t('BoxPassword.text')} />
|
|
66
|
+
<ListItemSecondaryAction>
|
|
67
|
+
<Switch
|
|
68
|
+
edge="end"
|
|
69
|
+
color="primary"
|
|
70
|
+
checked={toggle}
|
|
71
|
+
onChange={handlePasswordToggle}
|
|
72
|
+
/>
|
|
73
|
+
</ListItemSecondaryAction>
|
|
74
|
+
</ListItem>
|
|
75
|
+
</List>
|
|
76
|
+
{toggle && (
|
|
77
|
+
<div className="u-pt-half u-ph-1 u-pb-1">
|
|
78
|
+
<TextField
|
|
79
|
+
inputRef={inputRef}
|
|
80
|
+
label={t('BoxPassword.label')}
|
|
81
|
+
value={password}
|
|
82
|
+
error={displayHelper && !!helperText}
|
|
83
|
+
helperText={displayHelper && helperText}
|
|
84
|
+
onChange={handleChange}
|
|
85
|
+
onFocus={handleOnFocus}
|
|
86
|
+
onBlur={handleOnBlur}
|
|
87
|
+
fullWidth
|
|
88
|
+
type="text"
|
|
89
|
+
variant="outlined"
|
|
90
|
+
InputProps={{
|
|
91
|
+
endAdornment: (
|
|
92
|
+
<IconButton onClick={handleCopy}>
|
|
93
|
+
<Icon icon="copy" />
|
|
94
|
+
</IconButton>
|
|
95
|
+
)
|
|
96
|
+
}}
|
|
97
|
+
inputProps={inputProps}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</Box>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
BoxPassword.propTypes = {
|
|
106
|
+
onChange: PropTypes.func.isRequired,
|
|
107
|
+
password: PropTypes.string,
|
|
108
|
+
onToggle: PropTypes.func.isRequired,
|
|
109
|
+
toggle: PropTypes.bool.isRequired,
|
|
110
|
+
helperText: PropTypes.string,
|
|
111
|
+
inputProps: PropTypes.object
|
|
112
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { addDays } from 'date-fns'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
import Box from 'cozy-ui/transpiled/react/Box'
|
|
6
|
+
import Typography from 'cozy-ui/transpiled/react/Typography'
|
|
7
|
+
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
|
|
8
|
+
|
|
9
|
+
import { BoxDate } from './BoxDate'
|
|
10
|
+
import { BoxEditingRights } from './BoxEditingRights'
|
|
11
|
+
import { BoxPassword } from './BoxPassword'
|
|
12
|
+
|
|
13
|
+
const PASSWORD_MIN_LENGTH = 4
|
|
14
|
+
|
|
15
|
+
export const ShareRestrictionContentModal = ({
|
|
16
|
+
file,
|
|
17
|
+
// Date
|
|
18
|
+
selectedDate,
|
|
19
|
+
setSelectedDate,
|
|
20
|
+
dateToggle,
|
|
21
|
+
setDateToggle,
|
|
22
|
+
setIsValidDate,
|
|
23
|
+
// Password
|
|
24
|
+
helperTextPassword,
|
|
25
|
+
password,
|
|
26
|
+
setPassword,
|
|
27
|
+
passwordToggle,
|
|
28
|
+
setPasswordToggle,
|
|
29
|
+
setIsValidPassword,
|
|
30
|
+
// Editing rights
|
|
31
|
+
editingRights,
|
|
32
|
+
setEditingRights
|
|
33
|
+
}) => {
|
|
34
|
+
const { t } = useI18n()
|
|
35
|
+
|
|
36
|
+
const handlePasswordToggle = val => {
|
|
37
|
+
setPasswordToggle(val)
|
|
38
|
+
}
|
|
39
|
+
const handlePassword = value => {
|
|
40
|
+
setPassword(value)
|
|
41
|
+
setIsValidPassword(
|
|
42
|
+
value.trim().length === 0 || value.trim().length >= PASSWORD_MIN_LENGTH
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handleDateToggle = val => {
|
|
47
|
+
if (!val) {
|
|
48
|
+
setIsValidDate(true)
|
|
49
|
+
}
|
|
50
|
+
const defaultValue = val ? addDays(new Date(), 30) : null
|
|
51
|
+
setSelectedDate(defaultValue, true)
|
|
52
|
+
setDateToggle(val)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Box display="flex" flexDirection="column" gridGap="1rem">
|
|
57
|
+
<Typography variant="h4" className="u-mb-half u-ml-half">
|
|
58
|
+
{t('ShareRestrictionModal.title')}
|
|
59
|
+
</Typography>
|
|
60
|
+
|
|
61
|
+
<BoxEditingRights
|
|
62
|
+
file={file}
|
|
63
|
+
editingRights={editingRights}
|
|
64
|
+
setEditingRights={setEditingRights}
|
|
65
|
+
/>
|
|
66
|
+
<BoxDate
|
|
67
|
+
onChange={setSelectedDate}
|
|
68
|
+
date={selectedDate}
|
|
69
|
+
onToggle={handleDateToggle}
|
|
70
|
+
toggle={dateToggle}
|
|
71
|
+
/>
|
|
72
|
+
<BoxPassword
|
|
73
|
+
onChange={handlePassword}
|
|
74
|
+
password={password}
|
|
75
|
+
onToggle={handlePasswordToggle}
|
|
76
|
+
toggle={passwordToggle}
|
|
77
|
+
helperText={helperTextPassword}
|
|
78
|
+
inputProps={{ minLength: PASSWORD_MIN_LENGTH }}
|
|
79
|
+
/>
|
|
80
|
+
</Box>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ShareRestrictionContentModal.propTypes = {
|
|
85
|
+
file: PropTypes.object.isRequired,
|
|
86
|
+
// Date
|
|
87
|
+
selectedDate: PropTypes.object,
|
|
88
|
+
setSelectedDate: PropTypes.func.isRequired,
|
|
89
|
+
dateToggle: PropTypes.bool,
|
|
90
|
+
setDateToggle: PropTypes.func.isRequired,
|
|
91
|
+
setIsValidDate: PropTypes.func.isRequired,
|
|
92
|
+
// Password
|
|
93
|
+
helperTextPassword: PropTypes.string,
|
|
94
|
+
password: PropTypes.string,
|
|
95
|
+
setPassword: PropTypes.func.isRequired,
|
|
96
|
+
passwordToggle: PropTypes.bool.isRequired,
|
|
97
|
+
setPasswordToggle: PropTypes.func.isRequired,
|
|
98
|
+
setIsValidPassword: PropTypes.func.isRequired,
|
|
99
|
+
// Editing rights
|
|
100
|
+
editingRights: PropTypes.oneOf(['readOnly', 'write']),
|
|
101
|
+
setEditingRights: PropTypes.func.isRequired
|
|
102
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { addDays } from 'date-fns'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { useClient } from 'cozy-client'
|
|
6
|
+
import { getSharingLink as makeSharingLink } from 'cozy-client/dist/models/sharing'
|
|
7
|
+
import { isMobile } from 'cozy-device-helper'
|
|
8
|
+
import Button from 'cozy-ui/transpiled/react/Buttons'
|
|
9
|
+
import { ConfirmDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
|
|
10
|
+
import Icon from 'cozy-ui/transpiled/react/Icon'
|
|
11
|
+
import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
|
|
12
|
+
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'
|
|
13
|
+
|
|
14
|
+
import { ShareRestrictionContentModal } from './ShareRestrictionContentModal'
|
|
15
|
+
import {
|
|
16
|
+
copyToClipboard,
|
|
17
|
+
forwardFile,
|
|
18
|
+
updatePermissions,
|
|
19
|
+
makeTTL,
|
|
20
|
+
READ_ONLY_PERMS,
|
|
21
|
+
WRITE_PERMS,
|
|
22
|
+
revokePermissions
|
|
23
|
+
} from './helpers'
|
|
24
|
+
import { checkIsReadOnlyPermissions } from '../../helpers/permissions'
|
|
25
|
+
import { useSharingContext } from '../../hooks/useSharingContext'
|
|
26
|
+
|
|
27
|
+
const PASSWORD_MIN_LENGTH = 4
|
|
28
|
+
|
|
29
|
+
export const ShareRestrictionModal = ({ file, onClose }) => {
|
|
30
|
+
const client = useClient()
|
|
31
|
+
const { t } = useI18n()
|
|
32
|
+
const { showAlert } = useAlert()
|
|
33
|
+
const [password, setPassword] = useState('')
|
|
34
|
+
const [selectedDate, setSelectedDate] = useState(addDays(new Date(), 30))
|
|
35
|
+
const [isValidDate, setIsValidDate] = useState(true)
|
|
36
|
+
const [isValidPassword, setIsValidPassword] = useState(true)
|
|
37
|
+
const [dateToggle, setDateToggle] = useState(true)
|
|
38
|
+
const [passwordToggle, setPasswordToggle] = useState(false)
|
|
39
|
+
const [loading, setLoading] = useState(false)
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
documentType,
|
|
43
|
+
getDocumentPermissions,
|
|
44
|
+
updateDocumentPermissions,
|
|
45
|
+
shareByLink,
|
|
46
|
+
getSharingLink,
|
|
47
|
+
revokeSharingLink
|
|
48
|
+
} = useSharingContext()
|
|
49
|
+
|
|
50
|
+
const hasSharingLink = getSharingLink(file._id) !== null
|
|
51
|
+
const permissions = getDocumentPermissions(file._id)
|
|
52
|
+
const isReadOnlyPermissions = checkIsReadOnlyPermissions(permissions)
|
|
53
|
+
|
|
54
|
+
const [editingRights, setEditingRights] = useState(
|
|
55
|
+
isReadOnlyPermissions || permissions.length === 0 ? 'readOnly' : 'write'
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const isDesktopOrMobileWithoutShareAPI =
|
|
59
|
+
(isMobile() && !navigator.share) || !isMobile()
|
|
60
|
+
|
|
61
|
+
const helperTextPassword = !isValidPassword
|
|
62
|
+
? t('ShareRestrictionModal.invalidPasswordMessage', {
|
|
63
|
+
smart_count: PASSWORD_MIN_LENGTH - password.length
|
|
64
|
+
})
|
|
65
|
+
: null
|
|
66
|
+
|
|
67
|
+
const handleDateChange = (date, isValid) => {
|
|
68
|
+
setSelectedDate(date)
|
|
69
|
+
setIsValidDate(isValid)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleClick = async () => {
|
|
73
|
+
setLoading(true)
|
|
74
|
+
// If the file is not shared, we create a new sharing link
|
|
75
|
+
if (!hasSharingLink) {
|
|
76
|
+
const verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS
|
|
77
|
+
await shareByLink(file, { verbs })
|
|
78
|
+
const url = getSharingLink(file._id)
|
|
79
|
+
await copyToClipboard(url, { t, showAlert })
|
|
80
|
+
onClose()
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await updatePermissions({
|
|
85
|
+
file,
|
|
86
|
+
t,
|
|
87
|
+
editingRights,
|
|
88
|
+
documentType,
|
|
89
|
+
updateDocumentPermissions,
|
|
90
|
+
showAlert
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const ttl = makeTTL(dateToggle && selectedDate)
|
|
94
|
+
if (isDesktopOrMobileWithoutShareAPI) {
|
|
95
|
+
const url = await makeSharingLink(client, [file._id], {
|
|
96
|
+
ttl,
|
|
97
|
+
password
|
|
98
|
+
})
|
|
99
|
+
await copyToClipboard(url, { t, showAlert })
|
|
100
|
+
} else {
|
|
101
|
+
await forwardFile({
|
|
102
|
+
client,
|
|
103
|
+
file,
|
|
104
|
+
t,
|
|
105
|
+
ttl,
|
|
106
|
+
password,
|
|
107
|
+
showAlert
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
onClose()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const handleRevokeLink = async () => {
|
|
114
|
+
setLoading(true)
|
|
115
|
+
await revokePermissions({
|
|
116
|
+
file,
|
|
117
|
+
t,
|
|
118
|
+
documentType,
|
|
119
|
+
revokeSharingLink,
|
|
120
|
+
showAlert
|
|
121
|
+
})
|
|
122
|
+
onClose()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<ConfirmDialog
|
|
127
|
+
open
|
|
128
|
+
onClose={onClose}
|
|
129
|
+
content={
|
|
130
|
+
<ShareRestrictionContentModal
|
|
131
|
+
file={file}
|
|
132
|
+
// Date
|
|
133
|
+
dateToggle={dateToggle}
|
|
134
|
+
setDateToggle={setDateToggle}
|
|
135
|
+
setSelectedDate={handleDateChange}
|
|
136
|
+
selectedDate={selectedDate}
|
|
137
|
+
setIsValidDate={setIsValidDate}
|
|
138
|
+
// Password
|
|
139
|
+
helperTextPassword={helperTextPassword}
|
|
140
|
+
passwordToggle={passwordToggle}
|
|
141
|
+
setPasswordToggle={setPasswordToggle}
|
|
142
|
+
password={password}
|
|
143
|
+
setPassword={setPassword}
|
|
144
|
+
setIsValidPassword={setIsValidPassword}
|
|
145
|
+
// Editing rights
|
|
146
|
+
editingRights={editingRights}
|
|
147
|
+
setEditingRights={setEditingRights}
|
|
148
|
+
/>
|
|
149
|
+
}
|
|
150
|
+
actions={
|
|
151
|
+
<>
|
|
152
|
+
{hasSharingLink && (
|
|
153
|
+
<Button
|
|
154
|
+
label={t('Share.permissionLink.deactivate')}
|
|
155
|
+
variant="secondary"
|
|
156
|
+
color="error"
|
|
157
|
+
startIcon={<Icon icon="trash" />}
|
|
158
|
+
onClick={handleRevokeLink}
|
|
159
|
+
busy={loading}
|
|
160
|
+
/>
|
|
161
|
+
)}
|
|
162
|
+
<Button
|
|
163
|
+
label={t('ShareRestrictionModal.action.confirm')}
|
|
164
|
+
onClick={handleClick}
|
|
165
|
+
disabled={!isValidDate || !isValidPassword}
|
|
166
|
+
busy={loading}
|
|
167
|
+
/>
|
|
168
|
+
</>
|
|
169
|
+
}
|
|
170
|
+
/>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
ShareRestrictionModal.propTypes = {
|
|
175
|
+
file: PropTypes.object.isRequired,
|
|
176
|
+
onClose: PropTypes.func
|
|
177
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { getSharingLink } from 'cozy-client/dist/models/sharing'
|
|
2
|
+
import minilog from 'cozy-minilog'
|
|
3
|
+
|
|
4
|
+
const log = minilog('ShareRestrictionModal/helpers')
|
|
5
|
+
|
|
6
|
+
export const WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH']
|
|
7
|
+
export const READ_ONLY_PERMS = ['GET']
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Copy a string to the clipboard
|
|
11
|
+
*
|
|
12
|
+
* @param {string} value - The string saved to the clipboard
|
|
13
|
+
* @param {object} [options]
|
|
14
|
+
* @param {number} options.t - Translation function
|
|
15
|
+
* @param {Function} options.showAlert - Function to display an alert
|
|
16
|
+
*/
|
|
17
|
+
export const copyToClipboard = async (value, { t, showAlert } = {}) => {
|
|
18
|
+
if (!value) return false
|
|
19
|
+
try {
|
|
20
|
+
await navigator.clipboard.writeText(value)
|
|
21
|
+
showAlert({
|
|
22
|
+
message: t('copyReminderContent.success'),
|
|
23
|
+
severity: 'success',
|
|
24
|
+
variant: 'filled'
|
|
25
|
+
})
|
|
26
|
+
} catch (error) {
|
|
27
|
+
showAlert({
|
|
28
|
+
message: t('copyReminderContent.error'),
|
|
29
|
+
severity: 'error',
|
|
30
|
+
variant: 'filled'
|
|
31
|
+
})
|
|
32
|
+
log.error(
|
|
33
|
+
"Error in 'copyToClipboard' function when trying to copy to clipboard",
|
|
34
|
+
error
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Make a TTL string from a date
|
|
41
|
+
* @param {Date|string} selectedDate
|
|
42
|
+
* @returns {string} - TTL string in seconds (e.g. '1234s')
|
|
43
|
+
*/
|
|
44
|
+
export const makeTTL = selectedDate => {
|
|
45
|
+
if (!selectedDate) return
|
|
46
|
+
try {
|
|
47
|
+
let selectedDateConverted = selectedDate
|
|
48
|
+
if (typeof selectedDate === 'string') {
|
|
49
|
+
selectedDateConverted = new Date(selectedDate)
|
|
50
|
+
if (selectedDateConverted?.toString() === 'Invalid Date') {
|
|
51
|
+
throw new Error(`Invalid date: ${selectedDate}`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (selectedDateConverted instanceof Date) {
|
|
55
|
+
const now = new Date()
|
|
56
|
+
const ttl =
|
|
57
|
+
selectedDateConverted > now
|
|
58
|
+
? `${Math.round((selectedDateConverted - now) / 1000)}s`
|
|
59
|
+
: undefined
|
|
60
|
+
return ttl
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Invalid date: ${selectedDate}`)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
log.error(error)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* forwardFile - Triggers the download of one or multiple files by the browser
|
|
71
|
+
* @param {object} options
|
|
72
|
+
* @param {import('cozy-client/types/CozyClient').default} options.client
|
|
73
|
+
* @param {import('cozy-client/types/types').IOCozyFile} options.file File to download
|
|
74
|
+
* @param {Function} options.t i18n function
|
|
75
|
+
* @param {string} options.ttl Time to live of the sharing link
|
|
76
|
+
* @param {string} options.password Password of the sharing link
|
|
77
|
+
* @param {Function} options.showAlert - Function to display an alert
|
|
78
|
+
*/
|
|
79
|
+
export const forwardFile = async ({
|
|
80
|
+
client,
|
|
81
|
+
file,
|
|
82
|
+
t,
|
|
83
|
+
ttl,
|
|
84
|
+
password,
|
|
85
|
+
showAlert
|
|
86
|
+
}) => {
|
|
87
|
+
try {
|
|
88
|
+
// We currently support only one file at a time
|
|
89
|
+
const url = await getSharingLink(client, [file._id], { ttl, password })
|
|
90
|
+
const isZipFile = file.class === 'zip'
|
|
91
|
+
const shareData = {
|
|
92
|
+
title: t('shareFile.title', {
|
|
93
|
+
name: file.name,
|
|
94
|
+
smart_count: isZipFile ? 2 : 1
|
|
95
|
+
}),
|
|
96
|
+
text: t('shareFile.text', {
|
|
97
|
+
name: file.name,
|
|
98
|
+
smart_count: isZipFile ? 2 : 1
|
|
99
|
+
}),
|
|
100
|
+
url
|
|
101
|
+
}
|
|
102
|
+
navigator.share(shareData)
|
|
103
|
+
} catch (error) {
|
|
104
|
+
showAlert({
|
|
105
|
+
message: t('shareFile.error'),
|
|
106
|
+
severity: 'error',
|
|
107
|
+
variant: 'filled'
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* updatePermissions - Updates the permissions of a file
|
|
114
|
+
* @param {object} options
|
|
115
|
+
* @param {import('cozy-client/types/types').IOCozyFile} options.file File to update permissions
|
|
116
|
+
* @param {Function} options.t i18n function
|
|
117
|
+
* @param {string} options.documentType - Type of the document
|
|
118
|
+
* @param {'readOnly'|'write'} options.editingRights - Editing rights
|
|
119
|
+
* @param {Function} options.updateDocumentPermissions - Function to update permissions
|
|
120
|
+
* @param {Function} options.showAlert - Function to display an alert
|
|
121
|
+
*/
|
|
122
|
+
export const updatePermissions = async ({
|
|
123
|
+
file,
|
|
124
|
+
t,
|
|
125
|
+
editingRights,
|
|
126
|
+
documentType,
|
|
127
|
+
updateDocumentPermissions,
|
|
128
|
+
showAlert
|
|
129
|
+
}) => {
|
|
130
|
+
switch (editingRights) {
|
|
131
|
+
case 'readOnly':
|
|
132
|
+
try {
|
|
133
|
+
return updateDocumentPermissions(file, READ_ONLY_PERMS)
|
|
134
|
+
} catch (err) {
|
|
135
|
+
showAlert({
|
|
136
|
+
message: t(`${documentType}.share.shareByLink.permserror`),
|
|
137
|
+
severity: 'error',
|
|
138
|
+
variant: 'filled'
|
|
139
|
+
})
|
|
140
|
+
log.error(
|
|
141
|
+
"Error in 'readOnlyPermissionLink' function when trying to change permission",
|
|
142
|
+
err
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
break
|
|
146
|
+
case 'write':
|
|
147
|
+
try {
|
|
148
|
+
return updateDocumentPermissions(file, WRITE_PERMS)
|
|
149
|
+
} catch (err) {
|
|
150
|
+
showAlert({
|
|
151
|
+
message: t(`${documentType}.share.shareByLink.permserror`),
|
|
152
|
+
severity: 'error',
|
|
153
|
+
variant: 'filled'
|
|
154
|
+
})
|
|
155
|
+
log.error(
|
|
156
|
+
"Error in 'editPermissionLink' function when trying to change permission",
|
|
157
|
+
err
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* revokePermissions - Revokes the permissions of a file
|
|
166
|
+
* @param {object} options
|
|
167
|
+
* @param {import('cozy-client/types/types').IOCozyFile} options.file File to revoke permissions
|
|
168
|
+
* @param {Function} options.t i18n function
|
|
169
|
+
* @param {string} options.documentType - Type of the document
|
|
170
|
+
* @param {Function} options.revokeSharingLink - Function to revoke permissions
|
|
171
|
+
* @param {Function} options.showAlert - Function to display an alert
|
|
172
|
+
*/
|
|
173
|
+
export const revokePermissions = async ({
|
|
174
|
+
file,
|
|
175
|
+
t,
|
|
176
|
+
documentType,
|
|
177
|
+
revokeSharingLink,
|
|
178
|
+
showAlert
|
|
179
|
+
}) => {
|
|
180
|
+
try {
|
|
181
|
+
return revokeSharingLink(file)
|
|
182
|
+
} catch (err) {
|
|
183
|
+
showAlert({
|
|
184
|
+
message: t(`${documentType}.share.error.revoke`),
|
|
185
|
+
severity: 'error',
|
|
186
|
+
variant: 'filled'
|
|
187
|
+
})
|
|
188
|
+
log.error(
|
|
189
|
+
"Error in 'revokePermissions' function when trying to revoke sharing link",
|
|
190
|
+
err
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { makeTTL, revokePermissions, updatePermissions } from './helpers'
|
|
2
|
+
|
|
3
|
+
describe('ShareRestrictionModal/helpers', () => {
|
|
4
|
+
describe('makeTTL', () => {
|
|
5
|
+
it('sould return undefined', () => {
|
|
6
|
+
expect(makeTTL()).toBeUndefined()
|
|
7
|
+
expect(makeTTL(123)).toBeUndefined()
|
|
8
|
+
expect(makeTTL('abc')).toBeUndefined()
|
|
9
|
+
expect(makeTTL(new Date('2023-01-01T00:00:00.000Z'))).toBeUndefined()
|
|
10
|
+
expect(makeTTL(new Date('abc'))).toBeUndefined()
|
|
11
|
+
expect(makeTTL('2023-01-01T00:00:00.000Z')).toBeUndefined()
|
|
12
|
+
})
|
|
13
|
+
it('sould return TTL in seconds', () => {
|
|
14
|
+
expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined()
|
|
15
|
+
expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined()
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('updatePermissions', () => {
|
|
20
|
+
const documentType = 'Files'
|
|
21
|
+
it('should call updateDocumentPermissions with "readOnly" permission', async () => {
|
|
22
|
+
const updateDocumentPermissions = jest.fn()
|
|
23
|
+
const file = { _id: '123' }
|
|
24
|
+
const editingRights = 'readOnly'
|
|
25
|
+
|
|
26
|
+
await updatePermissions({
|
|
27
|
+
file,
|
|
28
|
+
t: jest.fn(),
|
|
29
|
+
editingRights,
|
|
30
|
+
documentType,
|
|
31
|
+
updateDocumentPermissions,
|
|
32
|
+
showAlert: jest.fn()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
expect(updateDocumentPermissions).toHaveBeenCalledWith(file, ['GET'])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should call updateDocumentPermissions with "write" permissions', async () => {
|
|
39
|
+
const updateDocumentPermissions = jest.fn()
|
|
40
|
+
const file = { _id: '123' }
|
|
41
|
+
const editingRights = 'write'
|
|
42
|
+
|
|
43
|
+
await updatePermissions({
|
|
44
|
+
file,
|
|
45
|
+
t: jest.fn(),
|
|
46
|
+
editingRights,
|
|
47
|
+
documentType,
|
|
48
|
+
updateDocumentPermissions,
|
|
49
|
+
showAlert: jest.fn()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
expect(updateDocumentPermissions).toHaveBeenCalledWith(file, [
|
|
53
|
+
'GET',
|
|
54
|
+
'POST',
|
|
55
|
+
'PUT',
|
|
56
|
+
'PATCH'
|
|
57
|
+
])
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('revokePermissions', () => {
|
|
62
|
+
const documentType = 'Files'
|
|
63
|
+
it('should call revokeSharingLink', async () => {
|
|
64
|
+
const revokeSharingLink = jest.fn()
|
|
65
|
+
const file = { _id: '123' }
|
|
66
|
+
|
|
67
|
+
await revokePermissions({
|
|
68
|
+
file,
|
|
69
|
+
t: jest.fn(),
|
|
70
|
+
documentType,
|
|
71
|
+
revokeSharingLink,
|
|
72
|
+
showAlert: jest.fn()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
expect(revokeSharingLink).toHaveBeenCalledWith(file)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
})
|