cozy-sharing 33.4.3 → 33.5.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 CHANGED
@@ -3,6 +3,19 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [33.5.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.5.0...cozy-sharing@33.5.1) (2026-06-17)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Avoid rendering email twice in recipient row ([800f091](https://github.com/cozy/cozy-libs/commit/800f0915853f167b47087b77ef1a4620a4615fa3))
11
+ - **cozy-sharing:** Prioritize email over instance URL ([4814ab3](https://github.com/cozy/cozy-libs/commit/4814ab34ec13cd8f53641dd333107f2648fb8e76))
12
+
13
+ # [33.5.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.3...cozy-sharing@33.5.0) (2026-06-17)
14
+
15
+ ### Features
16
+
17
+ - Add isOrgSharedDrive and canLeave helpers ([f93d4bb](https://github.com/cozy/cozy-libs/commit/f93d4bb37cd748a7e18db3a3ac67e1b84e819440))
18
+
6
19
  ## [33.4.3](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.2...cozy-sharing@33.4.3) (2026-06-16)
7
20
 
8
21
  ### Bug Fixes
@@ -41,7 +41,7 @@ import { getSharingObject, createSharingInStore, updateSharingInStore } from './
41
41
  import { getShortcode } from './helpers/shortcodes';
42
42
  import { SynchronousJobQueue } from './helpers/synchronousJobQueue';
43
43
  import { fetchApps } from './queries/queries';
44
- import reducer, { receiveSharings, addSharing, updateSharing, addSharingLink, updateSharingLink, revokeSharingLink, revokeRecipient, revokeGroup as revokeGroupFromState, revokeSelf, receivePaths, isOwner as _isOwner, isSharedDrive as _isSharedDrive, canReshare as _canReshare, getOwner as _getOwner, getRecipients as _getRecipients, getSharingById as _getSharingById, getExternalSharingIds, getSharingForSelf as _getSharingForSelf, getSharingType as _getSharingType, getSharingLink as _getSharingLink, getSharedDocIdsBySharings, getDocumentSharing, getDocumentPermissions as _getDocumentPermissions, hasSharedParent as _hasSharedParent, hasSharedChild as _hasSharedChild, getSharedParentPath as _getSharedParentPath, getSharedDriveSharingType, SHARING_TYPE } from './state';
44
+ import reducer, { receiveSharings, addSharing, updateSharing, addSharingLink, updateSharingLink, revokeSharingLink, revokeRecipient, revokeGroup as revokeGroupFromState, revokeSelf, receivePaths, isOwner as _isOwner, isSharedDrive as _isSharedDrive, isOrgSharedDrive as _isOrgSharedDrive, canLeave as _canLeave, canReshare as _canReshare, getOwner as _getOwner, getRecipients as _getRecipients, getSharingById as _getSharingById, getExternalSharingIds, getSharingForSelf as _getSharingForSelf, getSharingType as _getSharingType, getSharingLink as _getSharingLink, getSharedDocIdsBySharings, getDocumentSharing, getDocumentPermissions as _getDocumentPermissions, hasSharedParent as _hasSharedParent, hasSharedChild as _hasSharedChild, getSharedParentPath as _getSharedParentPath, getSharedDriveSharingType, SHARING_TYPE } from './state';
45
45
  var log = minilog('SharingProvider');
46
46
  var SHARING_DOCTYPE = 'io.cozy.sharings';
47
47
  var PERMISSION_DOCTYPE = 'io.cozy.permissions';
@@ -997,6 +997,12 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
997
997
  isSharedDrive: function isSharedDrive(docId) {
998
998
  return _isSharedDrive(_this2.state, docId);
999
999
  },
1000
+ isOrgSharedDrive: function isOrgSharedDrive(docId) {
1001
+ return _isOrgSharedDrive(_this2.state, docId);
1002
+ },
1003
+ canLeave: function canLeave(docId) {
1004
+ return _canLeave(_this2.state, docId);
1005
+ },
1000
1006
  canReshare: function canReshare(docId) {
1001
1007
  return _canReshare(_this2.state, docId, _instanceUri);
1002
1008
  },
@@ -33,7 +33,9 @@ var GroupRecipientDetailWithAccess = function GroupRecipientDetailWithAccess(_re
33
33
  secondary: /*#__PURE__*/React.createElement(MemberRecipientStatus, {
34
34
  status: recipient.status,
35
35
  isMe: isMe,
36
- instance: recipient.instance
36
+ instance: recipient.instance,
37
+ email: recipient.email,
38
+ name: recipient.name || recipient.public_name
37
39
  })
38
40
  }));
39
41
  })));
@@ -60,7 +60,9 @@ var MemberRecipient = function MemberRecipient(props) {
60
60
  secondary: /*#__PURE__*/React.createElement(MemberRecipientStatus, {
61
61
  status: status,
62
62
  isMe: isMe,
63
- instance: instance
63
+ instance: instance,
64
+ email: rest.email,
65
+ name: rest.name || rest.public_name
64
66
  })
65
67
  }), RightPart));
66
68
  };
@@ -40,6 +40,8 @@ var MemberRecipientLite = function MemberRecipientLite(_ref) {
40
40
  status: recipient.status,
41
41
  isMe: isMe,
42
42
  instance: recipient.instance,
43
+ email: recipient.email,
44
+ name: recipient.name || recipient.public_name,
43
45
  typographyProps: {
44
46
  noWrap: true
45
47
  }
@@ -1,9 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ import PropTypes from 'prop-types';
2
3
  import React from 'react';
3
- import Icon from 'cozy-ui/transpiled/react/Icon';
4
- import EyeIcon from 'cozy-ui/transpiled/react/Icons/Eye';
5
- import PaperplaneIcon from 'cozy-ui/transpiled/react/Icons/Paperplane';
6
- import ToTheCloudIcon from 'cozy-ui/transpiled/react/Icons/ToTheCloud';
7
4
  import Typography from 'cozy-ui/transpiled/react/Typography';
8
5
  import { useI18n } from 'twake-i18n';
9
6
 
@@ -11,6 +8,8 @@ var MemberRecipientStatus = function MemberRecipientStatus(_ref) {
11
8
  var status = _ref.status,
12
9
  isMe = _ref.isMe,
13
10
  instance = _ref.instance,
11
+ email = _ref.email,
12
+ name = _ref.name,
14
13
  typographyProps = _ref.typographyProps;
15
14
 
16
15
  var _useI18n = useI18n(),
@@ -18,29 +17,33 @@ var MemberRecipientStatus = function MemberRecipientStatus(_ref) {
18
17
 
19
18
  var isSendingEmail = !isMe && status === 'mail-not-sent';
20
19
  var isReady = isMe || status === 'ready' || status === 'owner';
21
- var text, icon;
20
+ var text;
22
21
 
23
22
  if (isReady) {
24
- text = instance;
25
- icon = ToTheCloudIcon;
23
+ // Hide the email when the parent already uses it as the primary text
24
+ // (getDisplayName falls back to email when the recipient has no name).
25
+ // Keep the instance visible when there is no email at all, so the user
26
+ // can still tell the recipient lives on a different Cozy instance.
27
+ text = isMe || name || !email ? email || instance : '';
26
28
  } else if (isSendingEmail) {
27
29
  text = t('Share.status.mail-not-sent');
28
- icon = PaperplaneIcon;
29
30
  } else {
30
31
  var supportedStatus = ['pending', 'seen'];
31
32
  text = supportedStatus.includes(status) ? t("Share.status.".concat(status)) : t('Share.status.pending');
32
- icon = status === 'seen' ? EyeIcon : PaperplaneIcon;
33
33
  }
34
34
 
35
- return /*#__PURE__*/React.createElement("div", {
36
- className: "u-flex u-flex-items-center"
37
- }, /*#__PURE__*/React.createElement(Icon, {
38
- icon: icon,
39
- size: 10
40
- }), "\xA0", /*#__PURE__*/React.createElement(Typography, _extends({
35
+ return /*#__PURE__*/React.createElement(Typography, _extends({
41
36
  variant: "caption",
42
37
  color: "textSecondary"
43
- }, typographyProps), text));
38
+ }, typographyProps), text);
44
39
  };
45
40
 
41
+ MemberRecipientStatus.propTypes = {
42
+ status: PropTypes.string,
43
+ isMe: PropTypes.bool,
44
+ instance: PropTypes.string,
45
+ email: PropTypes.string,
46
+ name: PropTypes.string,
47
+ typographyProps: PropTypes.object
48
+ };
46
49
  export default MemberRecipientStatus;
@@ -0,0 +1,102 @@
1
+ import { render } from '@testing-library/react';
2
+ import React from 'react';
3
+ import MemberRecipientStatus from './MemberRecipientStatus';
4
+ import AppLike from '../../../test/AppLike';
5
+ describe('MemberRecipientStatus component', function () {
6
+ var setup = function setup(props) {
7
+ return render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(MemberRecipientStatus, props)));
8
+ };
9
+
10
+ it('should show email when status is ready and recipient has a name', function () {
11
+ var _setup = setup({
12
+ status: 'ready',
13
+ isMe: false,
14
+ email: 'other@example.com',
15
+ instance: 'foo.mycozy.cloud',
16
+ name: 'Other Person'
17
+ }),
18
+ getByText = _setup.getByText;
19
+
20
+ expect(getByText('other@example.com')).toBeTruthy();
21
+ });
22
+ it('should render nothing when recipient has no name and status is ready', function () {
23
+ var _setup2 = setup({
24
+ status: 'ready',
25
+ isMe: false,
26
+ email: 'other@example.com',
27
+ instance: 'foo.mycozy.cloud'
28
+ }),
29
+ queryByText = _setup2.queryByText;
30
+
31
+ expect(queryByText('other@example.com')).toBe(null);
32
+ expect(queryByText('foo.mycozy.cloud')).toBe(null);
33
+ });
34
+ it('should show email when isMe is true regardless of status', function () {
35
+ var _setup3 = setup({
36
+ status: 'pending',
37
+ isMe: true,
38
+ email: 'me@example.com'
39
+ }),
40
+ getByText = _setup3.getByText;
41
+
42
+ expect(getByText('me@example.com')).toBeTruthy();
43
+ });
44
+ it('should fallback to instance when email is missing and status is ready', function () {
45
+ var _setup4 = setup({
46
+ status: 'ready',
47
+ isMe: false,
48
+ instance: 'foo.mycozy.cloud'
49
+ }),
50
+ getByText = _setup4.getByText;
51
+
52
+ expect(getByText('foo.mycozy.cloud')).toBeTruthy();
53
+ });
54
+ it('should show email when status is owner, not isMe, and recipient has a name', function () {
55
+ var _setup5 = setup({
56
+ status: 'owner',
57
+ isMe: false,
58
+ email: 'owner@example.com',
59
+ instance: 'foo.mycozy.cloud',
60
+ name: 'Owner Person'
61
+ }),
62
+ getByText = _setup5.getByText;
63
+
64
+ expect(getByText('owner@example.com')).toBeTruthy();
65
+ });
66
+ it('should show "Sending invitation mail..." when status is mail-not-sent and not isMe', function () {
67
+ var _setup6 = setup({
68
+ status: 'mail-not-sent',
69
+ isMe: false
70
+ }),
71
+ getByText = _setup6.getByText;
72
+
73
+ expect(getByText('Sending invitation mail...')).toBeTruthy();
74
+ });
75
+ it('should show "Invitation sent" when status is pending', function () {
76
+ var _setup7 = setup({
77
+ status: 'pending',
78
+ isMe: false
79
+ }),
80
+ getByText = _setup7.getByText;
81
+
82
+ expect(getByText('Invitation sent')).toBeTruthy();
83
+ });
84
+ it('should show "Invitation seen" when status is seen', function () {
85
+ var _setup8 = setup({
86
+ status: 'seen',
87
+ isMe: false
88
+ }),
89
+ getByText = _setup8.getByText;
90
+
91
+ expect(getByText('Invitation seen')).toBeTruthy();
92
+ });
93
+ it('should fallback to "Invitation sent" for unknown status', function () {
94
+ var _setup9 = setup({
95
+ status: 'unknown-status',
96
+ isMe: false
97
+ }),
98
+ getByText = _setup9.getByText;
99
+
100
+ expect(getByText('Invitation sent')).toBeTruthy();
101
+ });
102
+ });
package/dist/state.js CHANGED
@@ -415,6 +415,21 @@ export var isSharedDrive = function isSharedDrive(state, docId) {
415
415
 
416
416
  return false;
417
417
  };
418
+ export var isOrgSharedDrive = function isOrgSharedDrive(state, docId) {
419
+ if (state.byDocId[docId] && state.byDocId[docId].sharings.length !== 0) {
420
+ var sharing = getSharingById(state, state.byDocId[docId].sharings[0]);
421
+ return sharing.attributes.drive === true && sharing.attributes.org_drive === true;
422
+ }
423
+
424
+ return false;
425
+ };
426
+ export var canLeave = function canLeave(state, docId) {
427
+ if (isOrgSharedDrive(state, docId)) {
428
+ return false;
429
+ }
430
+
431
+ return true;
432
+ };
418
433
  export var canReshare = function canReshare(state, docId, instanceUri) {
419
434
  var sharing = getDocumentSharing(state, docId);
420
435
  var me = sharing.attributes.members.find(matchingInstanceName(instanceUri));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.4.3",
3
+ "version": "33.5.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "832c42dbcfdc5db673a0b225163b953031def5de"
86
+ "gitHead": "15b85b5aef628416e919342a040ec758783473f0"
87
87
  }
@@ -28,6 +28,8 @@ import reducer, {
28
28
  receivePaths,
29
29
  isOwner,
30
30
  isSharedDrive,
31
+ isOrgSharedDrive,
32
+ canLeave,
31
33
  canReshare,
32
34
  getOwner,
33
35
  getRecipients,
@@ -66,6 +68,8 @@ export class SharingProvider extends Component {
66
68
  documentType,
67
69
  isOwner: docId => isOwner(this.state, docId),
68
70
  isSharedDrive: docId => isSharedDrive(this.state, docId),
71
+ isOrgSharedDrive: docId => isOrgSharedDrive(this.state, docId),
72
+ canLeave: docId => canLeave(this.state, docId),
69
73
  canReshare: docId => canReshare(this.state, docId, instanceUri),
70
74
  getOwner: docId => getOwner(this.state, docId),
71
75
  getSharingType: docId => getSharingType(this.state, docId, instanceUri),
@@ -38,6 +38,8 @@ const GroupRecipientDetailWithAccess = ({ withAccess }) => {
38
38
  status={recipient.status}
39
39
  isMe={isMe}
40
40
  instance={recipient.instance}
41
+ email={recipient.email}
42
+ name={recipient.name || recipient.public_name}
41
43
  />
42
44
  }
43
45
  />
@@ -66,6 +66,8 @@ const MemberRecipient = props => {
66
66
  status={status}
67
67
  isMe={isMe}
68
68
  instance={instance}
69
+ email={rest.email}
70
+ name={rest.name || rest.public_name}
69
71
  />
70
72
  }
71
73
  />
@@ -38,6 +38,8 @@ const MemberRecipientLite = ({ recipient, isOwner, ...props }) => {
38
38
  status={recipient.status}
39
39
  isMe={isMe}
40
40
  instance={recipient.instance}
41
+ email={recipient.email}
42
+ name={recipient.name || recipient.public_name}
41
43
  typographyProps={{ noWrap: true }}
42
44
  />
43
45
  }
@@ -1,42 +1,51 @@
1
+ import PropTypes from 'prop-types'
1
2
  import React from 'react'
2
3
 
3
- import Icon from 'cozy-ui/transpiled/react/Icon'
4
- import EyeIcon from 'cozy-ui/transpiled/react/Icons/Eye'
5
- import PaperplaneIcon from 'cozy-ui/transpiled/react/Icons/Paperplane'
6
- import ToTheCloudIcon from 'cozy-ui/transpiled/react/Icons/ToTheCloud'
7
4
  import Typography from 'cozy-ui/transpiled/react/Typography'
8
5
  import { useI18n } from 'twake-i18n'
9
6
 
10
- const MemberRecipientStatus = ({ status, isMe, instance, typographyProps }) => {
7
+ const MemberRecipientStatus = ({
8
+ status,
9
+ isMe,
10
+ instance,
11
+ email,
12
+ name,
13
+ typographyProps
14
+ }) => {
11
15
  const { t } = useI18n()
12
16
 
13
17
  const isSendingEmail = !isMe && status === 'mail-not-sent'
14
18
  const isReady = isMe || status === 'ready' || status === 'owner'
15
- let text, icon
19
+ let text
16
20
  if (isReady) {
17
- text = instance
18
- icon = ToTheCloudIcon
21
+ // Hide the email when the parent already uses it as the primary text
22
+ // (getDisplayName falls back to email when the recipient has no name).
23
+ // Keep the instance visible when there is no email at all, so the user
24
+ // can still tell the recipient lives on a different Cozy instance.
25
+ text = isMe || name || !email ? email || instance : ''
19
26
  } else if (isSendingEmail) {
20
27
  text = t('Share.status.mail-not-sent')
21
- icon = PaperplaneIcon
22
28
  } else {
23
29
  const supportedStatus = ['pending', 'seen']
24
30
  text = supportedStatus.includes(status)
25
31
  ? t(`Share.status.${status}`)
26
32
  : t('Share.status.pending')
27
-
28
- icon = status === 'seen' ? EyeIcon : PaperplaneIcon
29
33
  }
30
34
 
31
35
  return (
32
- <div className="u-flex u-flex-items-center">
33
- <Icon icon={icon} size={10} />
34
- &nbsp;
35
- <Typography variant="caption" color="textSecondary" {...typographyProps}>
36
- {text}
37
- </Typography>
38
- </div>
36
+ <Typography variant="caption" color="textSecondary" {...typographyProps}>
37
+ {text}
38
+ </Typography>
39
39
  )
40
40
  }
41
41
 
42
+ MemberRecipientStatus.propTypes = {
43
+ status: PropTypes.string,
44
+ isMe: PropTypes.bool,
45
+ instance: PropTypes.string,
46
+ email: PropTypes.string,
47
+ name: PropTypes.string,
48
+ typographyProps: PropTypes.object
49
+ }
50
+
42
51
  export default MemberRecipientStatus
@@ -0,0 +1,97 @@
1
+ import { render } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import MemberRecipientStatus from './MemberRecipientStatus'
5
+ import AppLike from '../../../test/AppLike'
6
+
7
+ describe('MemberRecipientStatus component', () => {
8
+ const setup = props =>
9
+ render(
10
+ <AppLike>
11
+ <MemberRecipientStatus {...props} />
12
+ </AppLike>
13
+ )
14
+
15
+ it('should show email when status is ready and recipient has a name', () => {
16
+ const { getByText } = setup({
17
+ status: 'ready',
18
+ isMe: false,
19
+ email: 'other@example.com',
20
+ instance: 'foo.mycozy.cloud',
21
+ name: 'Other Person'
22
+ })
23
+ expect(getByText('other@example.com')).toBeTruthy()
24
+ })
25
+
26
+ it('should render nothing when recipient has no name and status is ready', () => {
27
+ const { queryByText } = setup({
28
+ status: 'ready',
29
+ isMe: false,
30
+ email: 'other@example.com',
31
+ instance: 'foo.mycozy.cloud'
32
+ })
33
+ expect(queryByText('other@example.com')).toBe(null)
34
+ expect(queryByText('foo.mycozy.cloud')).toBe(null)
35
+ })
36
+
37
+ it('should show email when isMe is true regardless of status', () => {
38
+ const { getByText } = setup({
39
+ status: 'pending',
40
+ isMe: true,
41
+ email: 'me@example.com'
42
+ })
43
+ expect(getByText('me@example.com')).toBeTruthy()
44
+ })
45
+
46
+ it('should fallback to instance when email is missing and status is ready', () => {
47
+ const { getByText } = setup({
48
+ status: 'ready',
49
+ isMe: false,
50
+ instance: 'foo.mycozy.cloud'
51
+ })
52
+ expect(getByText('foo.mycozy.cloud')).toBeTruthy()
53
+ })
54
+
55
+ it('should show email when status is owner, not isMe, and recipient has a name', () => {
56
+ const { getByText } = setup({
57
+ status: 'owner',
58
+ isMe: false,
59
+ email: 'owner@example.com',
60
+ instance: 'foo.mycozy.cloud',
61
+ name: 'Owner Person'
62
+ })
63
+ expect(getByText('owner@example.com')).toBeTruthy()
64
+ })
65
+
66
+ it('should show "Sending invitation mail..." when status is mail-not-sent and not isMe', () => {
67
+ const { getByText } = setup({
68
+ status: 'mail-not-sent',
69
+ isMe: false
70
+ })
71
+ expect(getByText('Sending invitation mail...')).toBeTruthy()
72
+ })
73
+
74
+ it('should show "Invitation sent" when status is pending', () => {
75
+ const { getByText } = setup({
76
+ status: 'pending',
77
+ isMe: false
78
+ })
79
+ expect(getByText('Invitation sent')).toBeTruthy()
80
+ })
81
+
82
+ it('should show "Invitation seen" when status is seen', () => {
83
+ const { getByText } = setup({
84
+ status: 'seen',
85
+ isMe: false
86
+ })
87
+ expect(getByText('Invitation seen')).toBeTruthy()
88
+ })
89
+
90
+ it('should fallback to "Invitation sent" for unknown status', () => {
91
+ const { getByText } = setup({
92
+ status: 'unknown-status',
93
+ isMe: false
94
+ })
95
+ expect(getByText('Invitation sent')).toBeTruthy()
96
+ })
97
+ })
package/src/state.js CHANGED
@@ -329,6 +329,24 @@ export const isSharedDrive = (state, docId) => {
329
329
  return false
330
330
  }
331
331
 
332
+ export const isOrgSharedDrive = (state, docId) => {
333
+ if (state.byDocId[docId] && state.byDocId[docId].sharings.length !== 0) {
334
+ const sharing = getSharingById(state, state.byDocId[docId].sharings[0])
335
+
336
+ return (
337
+ sharing.attributes.drive === true && sharing.attributes.org_drive === true
338
+ )
339
+ }
340
+ return false
341
+ }
342
+
343
+ export const canLeave = (state, docId) => {
344
+ if (isOrgSharedDrive(state, docId)) {
345
+ return false
346
+ }
347
+ return true
348
+ }
349
+
332
350
  export const canReshare = (state, docId, instanceUri) => {
333
351
  const sharing = getDocumentSharing(state, docId)
334
352
  const me = sharing.attributes.members.find(matchingInstanceName(instanceUri))