cozy-sharing 33.4.0 → 33.4.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 +6 -0
- package/dist/components/Avatar/MemberAvatar.js +21 -5
- package/dist/components/Avatar/MemberAvatar.spec.js +77 -0
- package/dist/components/Recipient/OwnerRecipientDefault.js +8 -1
- package/dist/components/Recipient/OwnerRecipientDefaultLite.js +5 -1
- package/package.json +2 -2
- package/src/components/Avatar/MemberAvatar.jsx +19 -4
- package/src/components/Avatar/MemberAvatar.spec.jsx +80 -0
- package/src/components/Recipient/OwnerRecipientDefault.jsx +8 -0
- package/src/components/Recipient/OwnerRecipientDefaultLite.jsx +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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.4.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.0...cozy-sharing@33.4.1) (2026-06-15)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **cozy-sharing:** Keep the owner avatar visible in the share modal ([f63ae1d](https://github.com/cozy/cozy-libs/commit/f63ae1de24de5ca7746b0c6da579f52fa5593806))
|
|
11
|
+
|
|
6
12
|
# [33.4.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.4...cozy-sharing@33.4.0) (2026-06-15)
|
|
7
13
|
|
|
8
14
|
### Bug Fixes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
3
|
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
|
|
3
4
|
var _excluded = ["recipient"];
|
|
4
5
|
|
|
@@ -6,7 +7,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
|
|
|
6
7
|
|
|
7
8
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
8
9
|
|
|
9
|
-
import React from 'react';
|
|
10
|
+
import React, { useState } from 'react';
|
|
10
11
|
import { useClient } from 'cozy-client';
|
|
11
12
|
import Avatar from 'cozy-ui/transpiled/react/Avatar';
|
|
12
13
|
import logger from '../../logger';
|
|
@@ -16,11 +17,19 @@ var MemberAvatar = function MemberAvatar(_ref) {
|
|
|
16
17
|
var recipient = _ref.recipient,
|
|
17
18
|
rest = _objectWithoutProperties(_ref, _excluded);
|
|
18
19
|
|
|
19
|
-
var client = useClient(); //
|
|
20
|
+
var client = useClient(); // Remember which image url failed to load so we can fall back to the
|
|
21
|
+
// initials. Storing the url (instead of a boolean) means a new url is
|
|
22
|
+
// retried automatically, without an effect to reset the error state.
|
|
23
|
+
|
|
24
|
+
var _useState = useState(null),
|
|
25
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
26
|
+
erroredSrc = _useState2[0],
|
|
27
|
+
setErroredSrc = _useState2[1]; // There are cases when due to apparent memory leaks, the recipient does not exist
|
|
20
28
|
// This will trigger an unhanded error in the Avatar component and crash the app
|
|
21
29
|
// At the moment, we are not sure where is the root cause of this cascading undefined props
|
|
22
30
|
// Nevertheless, we can prevent the crash by returning null if the recipient is undefined
|
|
23
31
|
|
|
32
|
+
|
|
24
33
|
if (!recipient) {
|
|
25
34
|
// eslint-disable-next-line
|
|
26
35
|
logger.warn('RecipientAvatar: recipient is missing, see props:', _objectSpread({
|
|
@@ -40,11 +49,18 @@ var MemberAvatar = function MemberAvatar(_ref) {
|
|
|
40
49
|
*/
|
|
41
50
|
|
|
42
51
|
|
|
43
|
-
var image = recipient.avatarPath && recipient.status ? "".concat(client.options.uri).concat(recipient.avatarPath, "
|
|
44
|
-
|
|
52
|
+
var image = recipient.avatarPath && recipient.status ? "".concat(client.options.uri).concat(recipient.avatarPath).concat(recipient.avatarPath.includes('?') ? '&' : '?', "v=").concat(recipient.status) : null; // The avatar can become unreachable (eg. the related sharing is revoked
|
|
53
|
+
// while its members are still rendered), in which case the image request
|
|
54
|
+
// fails. Without a fallback the browser shows a broken-image icon, so we
|
|
55
|
+
// render the initials instead.
|
|
56
|
+
|
|
57
|
+
return /*#__PURE__*/React.createElement(Avatar, rest, image && erroredSrc !== image ? /*#__PURE__*/React.createElement("img", {
|
|
45
58
|
width: "100%",
|
|
46
59
|
height: "100%",
|
|
47
|
-
src: image
|
|
60
|
+
src: image,
|
|
61
|
+
onError: function onError() {
|
|
62
|
+
return setErroredSrc(image);
|
|
63
|
+
}
|
|
48
64
|
}) : getInitials(recipient));
|
|
49
65
|
};
|
|
50
66
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { createMockClient } from 'cozy-client';
|
|
4
|
+
import { MemberAvatar } from './MemberAvatar';
|
|
5
|
+
import AppLike from '../../../test/AppLike';
|
|
6
|
+
describe('MemberAvatar', function () {
|
|
7
|
+
var client = createMockClient({});
|
|
8
|
+
client.options = {
|
|
9
|
+
uri: 'http://cozy.example.com'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
var setup = function setup(recipient) {
|
|
13
|
+
return render( /*#__PURE__*/React.createElement(AppLike, {
|
|
14
|
+
client: client
|
|
15
|
+
}, /*#__PURE__*/React.createElement(MemberAvatar, {
|
|
16
|
+
recipient: recipient
|
|
17
|
+
})));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
it('renders nothing when the recipient is missing', function () {
|
|
21
|
+
var _setup = setup(undefined),
|
|
22
|
+
container = _setup.container; // The component returns null; only the provider wrappers remain, with no
|
|
23
|
+
// avatar rendered inside.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
expect(container.querySelector('[class*="MuiAvatar-root"]')).toBeNull();
|
|
27
|
+
expect(container.querySelector('img')).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it('renders the avatar image when the recipient has an avatarPath and a status', function () {
|
|
30
|
+
var _setup2 = setup({
|
|
31
|
+
public_name: 'Bob',
|
|
32
|
+
status: 'owner',
|
|
33
|
+
avatarPath: '/sharings/123/recipients/0/avatar'
|
|
34
|
+
}),
|
|
35
|
+
container = _setup2.container;
|
|
36
|
+
|
|
37
|
+
var img = container.querySelector('img');
|
|
38
|
+
expect(img).toBeTruthy();
|
|
39
|
+
expect(img.getAttribute('src')).toBe('http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner');
|
|
40
|
+
});
|
|
41
|
+
it('appends the cache-busting param with & when avatarPath already has a query string', function () {
|
|
42
|
+
var _setup3 = setup({
|
|
43
|
+
public_name: 'Bob',
|
|
44
|
+
status: 'owner',
|
|
45
|
+
avatarPath: '/public/avatar?fallback=initials'
|
|
46
|
+
}),
|
|
47
|
+
container = _setup3.container;
|
|
48
|
+
|
|
49
|
+
expect(container.querySelector('img').getAttribute('src')).toBe('http://cozy.example.com/public/avatar?fallback=initials&v=owner');
|
|
50
|
+
});
|
|
51
|
+
it('renders the initials when there is no avatarPath', function () {
|
|
52
|
+
var _setup4 = setup({
|
|
53
|
+
public_name: 'Bob',
|
|
54
|
+
status: 'owner'
|
|
55
|
+
}),
|
|
56
|
+
container = _setup4.container,
|
|
57
|
+
getByText = _setup4.getByText;
|
|
58
|
+
|
|
59
|
+
expect(container.querySelector('img')).toBeNull();
|
|
60
|
+
expect(getByText('B')).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
it('falls back to the initials when the avatar image fails to load', function () {
|
|
63
|
+
var _setup5 = setup({
|
|
64
|
+
public_name: 'Bob',
|
|
65
|
+
status: 'owner',
|
|
66
|
+
avatarPath: '/sharings/123/recipients/0/avatar'
|
|
67
|
+
}),
|
|
68
|
+
container = _setup5.container,
|
|
69
|
+
getByText = _setup5.getByText;
|
|
70
|
+
|
|
71
|
+
var img = container.querySelector('img');
|
|
72
|
+
expect(img).toBeTruthy();
|
|
73
|
+
fireEvent.error(img);
|
|
74
|
+
expect(container.querySelector('img')).toBeNull();
|
|
75
|
+
expect(getByText('B')).toBeTruthy();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client';
|
|
3
3
|
import MemberRecipient from './MemberRecipient';
|
|
4
|
-
import { buildInstanceSettingsQuery } from '../../queries/queries';
|
|
4
|
+
import { buildInstanceSettingsQuery } from '../../queries/queries'; // When the owner is not part of a sharing's members (eg. the folder only has
|
|
5
|
+
// a link), there is no per-sharing avatar to reference. The instance's public
|
|
6
|
+
// avatar still holds the owner's picture. fallback=initials makes the stack
|
|
7
|
+
// serve a generated initials avatar (instead of the app icon) when no picture
|
|
8
|
+
// has been uploaded, matching how avatars are rendered everywhere else.
|
|
9
|
+
|
|
10
|
+
var OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials';
|
|
5
11
|
|
|
6
12
|
var OwnerRecipientDefault = function OwnerRecipientDefault() {
|
|
7
13
|
var _instanceSettingsResu;
|
|
@@ -13,6 +19,7 @@ var OwnerRecipientDefault = function OwnerRecipientDefault() {
|
|
|
13
19
|
isOwner: true,
|
|
14
20
|
status: "owner",
|
|
15
21
|
instance: client.options.uri,
|
|
22
|
+
avatarPath: OWNER_PUBLIC_AVATAR_PATH,
|
|
16
23
|
public_name: instanceSettingsResult === null || instanceSettingsResult === void 0 || (_instanceSettingsResu = instanceSettingsResult.data) === null || _instanceSettingsResu === void 0 || (_instanceSettingsResu = _instanceSettingsResu.attributes) === null || _instanceSettingsResu === void 0 ? void 0 : _instanceSettingsResu.public_name
|
|
17
24
|
});
|
|
18
25
|
};
|
|
@@ -2,7 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client';
|
|
3
3
|
import MemberRecipientLite from './MemberRecipientLite';
|
|
4
4
|
import withLocales from '../../hoc/withLocales';
|
|
5
|
-
import { buildInstanceSettingsQuery } from '../../queries/queries';
|
|
5
|
+
import { buildInstanceSettingsQuery } from '../../queries/queries'; // See OwnerRecipientDefault: show the instance public avatar (with an initials
|
|
6
|
+
// fallback) when the owner has no per-sharing avatar to reference.
|
|
7
|
+
|
|
8
|
+
var OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials';
|
|
6
9
|
|
|
7
10
|
var OwnerRecipientDefaultLite = function OwnerRecipientDefaultLite() {
|
|
8
11
|
var _instanceSettingsResu;
|
|
@@ -14,6 +17,7 @@ var OwnerRecipientDefaultLite = function OwnerRecipientDefaultLite() {
|
|
|
14
17
|
recipient: {
|
|
15
18
|
status: 'owner',
|
|
16
19
|
instance: client.options.uri,
|
|
20
|
+
avatarPath: OWNER_PUBLIC_AVATAR_PATH,
|
|
17
21
|
public_name: instanceSettingsResult === null || instanceSettingsResult === void 0 || (_instanceSettingsResu = instanceSettingsResult.data) === null || _instanceSettingsResu === void 0 || (_instanceSettingsResu = _instanceSettingsResu.attributes) === null || _instanceSettingsResu === void 0 ? void 0 : _instanceSettingsResu.public_name
|
|
18
22
|
},
|
|
19
23
|
isOwner: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "33.4.
|
|
3
|
+
"version": "33.4.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": "
|
|
86
|
+
"gitHead": "a2e29d6c728ebf0fdc890666a9ec3505e90551ed"
|
|
87
87
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useClient } from 'cozy-client'
|
|
4
4
|
import Avatar from 'cozy-ui/transpiled/react/Avatar'
|
|
@@ -8,6 +8,10 @@ import { getInitials } from '../../models'
|
|
|
8
8
|
|
|
9
9
|
const MemberAvatar = ({ recipient, ...rest }) => {
|
|
10
10
|
const client = useClient()
|
|
11
|
+
// Remember which image url failed to load so we can fall back to the
|
|
12
|
+
// initials. Storing the url (instead of a boolean) means a new url is
|
|
13
|
+
// retried automatically, without an effect to reset the error state.
|
|
14
|
+
const [erroredSrc, setErroredSrc] = useState(null)
|
|
11
15
|
|
|
12
16
|
// There are cases when due to apparent memory leaks, the recipient does not exist
|
|
13
17
|
// This will trigger an unhanded error in the Avatar component and crash the app
|
|
@@ -31,13 +35,24 @@ const MemberAvatar = ({ recipient, ...rest }) => {
|
|
|
31
35
|
*/
|
|
32
36
|
const image =
|
|
33
37
|
recipient.avatarPath && recipient.status
|
|
34
|
-
? `${client.options.uri}${recipient.avatarPath}
|
|
38
|
+
? `${client.options.uri}${recipient.avatarPath}${
|
|
39
|
+
recipient.avatarPath.includes('?') ? '&' : '?'
|
|
40
|
+
}v=${recipient.status}`
|
|
35
41
|
: null
|
|
36
42
|
|
|
43
|
+
// The avatar can become unreachable (eg. the related sharing is revoked
|
|
44
|
+
// while its members are still rendered), in which case the image request
|
|
45
|
+
// fails. Without a fallback the browser shows a broken-image icon, so we
|
|
46
|
+
// render the initials instead.
|
|
37
47
|
return (
|
|
38
48
|
<Avatar {...rest}>
|
|
39
|
-
{image ? (
|
|
40
|
-
<img
|
|
49
|
+
{image && erroredSrc !== image ? (
|
|
50
|
+
<img
|
|
51
|
+
width="100%"
|
|
52
|
+
height="100%"
|
|
53
|
+
src={image}
|
|
54
|
+
onError={() => setErroredSrc(image)}
|
|
55
|
+
/>
|
|
41
56
|
) : (
|
|
42
57
|
getInitials(recipient)
|
|
43
58
|
)}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { render, fireEvent } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { createMockClient } from 'cozy-client'
|
|
5
|
+
|
|
6
|
+
import { MemberAvatar } from './MemberAvatar'
|
|
7
|
+
import AppLike from '../../../test/AppLike'
|
|
8
|
+
|
|
9
|
+
describe('MemberAvatar', () => {
|
|
10
|
+
const client = createMockClient({})
|
|
11
|
+
client.options = { uri: 'http://cozy.example.com' }
|
|
12
|
+
|
|
13
|
+
const setup = recipient =>
|
|
14
|
+
render(
|
|
15
|
+
<AppLike client={client}>
|
|
16
|
+
<MemberAvatar recipient={recipient} />
|
|
17
|
+
</AppLike>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
it('renders nothing when the recipient is missing', () => {
|
|
21
|
+
const { container } = setup(undefined)
|
|
22
|
+
|
|
23
|
+
// The component returns null; only the provider wrappers remain, with no
|
|
24
|
+
// avatar rendered inside.
|
|
25
|
+
expect(container.querySelector('[class*="MuiAvatar-root"]')).toBeNull()
|
|
26
|
+
expect(container.querySelector('img')).toBeNull()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('renders the avatar image when the recipient has an avatarPath and a status', () => {
|
|
30
|
+
const { container } = setup({
|
|
31
|
+
public_name: 'Bob',
|
|
32
|
+
status: 'owner',
|
|
33
|
+
avatarPath: '/sharings/123/recipients/0/avatar'
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const img = container.querySelector('img')
|
|
37
|
+
expect(img).toBeTruthy()
|
|
38
|
+
expect(img.getAttribute('src')).toBe(
|
|
39
|
+
'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('appends the cache-busting param with & when avatarPath already has a query string', () => {
|
|
44
|
+
const { container } = setup({
|
|
45
|
+
public_name: 'Bob',
|
|
46
|
+
status: 'owner',
|
|
47
|
+
avatarPath: '/public/avatar?fallback=initials'
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect(container.querySelector('img').getAttribute('src')).toBe(
|
|
51
|
+
'http://cozy.example.com/public/avatar?fallback=initials&v=owner'
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('renders the initials when there is no avatarPath', () => {
|
|
56
|
+
const { container, getByText } = setup({
|
|
57
|
+
public_name: 'Bob',
|
|
58
|
+
status: 'owner'
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(container.querySelector('img')).toBeNull()
|
|
62
|
+
expect(getByText('B')).toBeTruthy()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('falls back to the initials when the avatar image fails to load', () => {
|
|
66
|
+
const { container, getByText } = setup({
|
|
67
|
+
public_name: 'Bob',
|
|
68
|
+
status: 'owner',
|
|
69
|
+
avatarPath: '/sharings/123/recipients/0/avatar'
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const img = container.querySelector('img')
|
|
73
|
+
expect(img).toBeTruthy()
|
|
74
|
+
|
|
75
|
+
fireEvent.error(img)
|
|
76
|
+
|
|
77
|
+
expect(container.querySelector('img')).toBeNull()
|
|
78
|
+
expect(getByText('B')).toBeTruthy()
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -5,6 +5,13 @@ import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client'
|
|
|
5
5
|
import MemberRecipient from './MemberRecipient'
|
|
6
6
|
import { buildInstanceSettingsQuery } from '../../queries/queries'
|
|
7
7
|
|
|
8
|
+
// When the owner is not part of a sharing's members (eg. the folder only has
|
|
9
|
+
// a link), there is no per-sharing avatar to reference. The instance's public
|
|
10
|
+
// avatar still holds the owner's picture. fallback=initials makes the stack
|
|
11
|
+
// serve a generated initials avatar (instead of the app icon) when no picture
|
|
12
|
+
// has been uploaded, matching how avatars are rendered everywhere else.
|
|
13
|
+
const OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials'
|
|
14
|
+
|
|
8
15
|
const OwnerRecipientDefault = () => {
|
|
9
16
|
const client = useClient()
|
|
10
17
|
|
|
@@ -20,6 +27,7 @@ const OwnerRecipientDefault = () => {
|
|
|
20
27
|
isOwner={true}
|
|
21
28
|
status="owner"
|
|
22
29
|
instance={client.options.uri}
|
|
30
|
+
avatarPath={OWNER_PUBLIC_AVATAR_PATH}
|
|
23
31
|
public_name={instanceSettingsResult?.data?.attributes?.public_name}
|
|
24
32
|
/>
|
|
25
33
|
)
|
|
@@ -6,6 +6,10 @@ import MemberRecipientLite from './MemberRecipientLite'
|
|
|
6
6
|
import withLocales from '../../hoc/withLocales'
|
|
7
7
|
import { buildInstanceSettingsQuery } from '../../queries/queries'
|
|
8
8
|
|
|
9
|
+
// See OwnerRecipientDefault: show the instance public avatar (with an initials
|
|
10
|
+
// fallback) when the owner has no per-sharing avatar to reference.
|
|
11
|
+
const OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials'
|
|
12
|
+
|
|
9
13
|
const OwnerRecipientDefaultLite = () => {
|
|
10
14
|
const client = useClient()
|
|
11
15
|
|
|
@@ -21,6 +25,7 @@ const OwnerRecipientDefaultLite = () => {
|
|
|
21
25
|
recipient={{
|
|
22
26
|
status: 'owner',
|
|
23
27
|
instance: client.options.uri,
|
|
28
|
+
avatarPath: OWNER_PUBLIC_AVATAR_PATH,
|
|
24
29
|
public_name: instanceSettingsResult?.data?.attributes?.public_name
|
|
25
30
|
}}
|
|
26
31
|
isOwner={true}
|