cozy-viewer 28.1.16 → 28.1.18

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,16 @@
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
+ ## [28.1.18](https://github.com/cozy/cozy-libs/compare/cozy-viewer@28.1.17...cozy-viewer@28.1.18) (2026-06-23)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-viewer:** Show inherited sharing recipients ([5da160a](https://github.com/cozy/cozy-libs/commit/5da160afce3f81f10969607bee76e58197c466a8))
11
+
12
+ ## [28.1.17](https://github.com/cozy/cozy-libs/compare/cozy-viewer@28.1.16...cozy-viewer@28.1.17) (2026-06-22)
13
+
14
+ **Note:** Version bump only for package cozy-viewer
15
+
6
16
  ## [28.1.16](https://github.com/cozy/cozy-libs/compare/cozy-viewer@28.1.15...cozy-viewer@28.1.16) (2026-06-17)
7
17
 
8
18
  **Note:** Version bump only for package cozy-viewer
@@ -7,6 +7,8 @@ Object.defineProperty(exports, "__esModule", {
7
7
  });
8
8
  exports.default = void 0;
9
9
 
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+
10
12
  var _propTypes = _interopRequireDefault(require("prop-types"));
11
13
 
12
14
  var _react = _interopRequireDefault(require("react"));
@@ -37,7 +39,15 @@ var _withViewerLocales = require("../hoc/withViewerLocales");
37
39
 
38
40
  var _ShareModalProvider = require("../providers/ShareModalProvider");
39
41
 
42
+ var _queries = require("../queries");
43
+
44
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
45
+
46
+ 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) { (0, _defineProperty2.default)(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; }
47
+
40
48
  var Sharing = function Sharing(_ref) {
49
+ var _sharedParentFolders$;
50
+
41
51
  var file = _ref.file,
42
52
  t = _ref.t;
43
53
  var client = (0, _cozyClient.useClient)();
@@ -51,16 +61,29 @@ var Sharing = function Sharing(_ref) {
51
61
  getSharingLink = _useSharingContext.getSharingLink,
52
62
  allLoaded = _useSharingContext.allLoaded,
53
63
  getRecipients = _useSharingContext.getRecipients,
64
+ getSharedParentPath = _useSharingContext.getSharedParentPath,
65
+ hasSharedParent = _useSharingContext.hasSharedParent,
54
66
  isOwner = _useSharingContext.isOwner;
55
67
 
56
68
  var sharedDriveSharing = file.driveId ? getSharingById(file.driveId) : null;
57
- var recipients = sharedDriveSharing ? (0, _cozySharing.getRecipientsFromSharing)(sharedDriveSharing, file._id) : getRecipients(file._id);
58
- var permissions = getDocumentPermissions(file._id);
59
- var link = getSharingLink(file._id);
69
+ var sharedParentPath = !sharedDriveSharing && file.path && hasSharedParent(file.path) ? getSharedParentPath(file.path) : null;
70
+ var sharedParentQuery = (0, _queries.buildFolderByPathQuery)(sharedParentPath);
71
+
72
+ var _useQuery = (0, _cozyClient.useQuery)(sharedParentQuery.definition, _objectSpread(_objectSpread({}, sharedParentQuery.options), {}, {
73
+ enabled: !!sharedParentPath
74
+ })),
75
+ sharedParentFolders = _useQuery.data,
76
+ sharedParentFetchStatus = _useQuery.fetchStatus;
77
+
78
+ var sharingReferenceId = !sharedDriveSharing && sharedParentPath && sharedParentFolders !== null && sharedParentFolders !== void 0 && (_sharedParentFolders$ = sharedParentFolders[0]) !== null && _sharedParentFolders$ !== void 0 && _sharedParentFolders$._id ? sharedParentFolders[0]._id : file._id;
79
+ if (sharedParentPath && sharedParentFetchStatus !== 'loaded') return null;
80
+ var recipients = sharedDriveSharing ? (0, _cozySharing.getRecipientsFromSharing)(sharedDriveSharing, file._id) : getRecipients(sharingReferenceId);
81
+ var permissions = getDocumentPermissions(sharingReferenceId);
82
+ var link = getSharingLink(sharingReferenceId);
60
83
  var owner = recipients.find(function (recipient) {
61
84
  return recipient.status === 'owner';
62
85
  });
63
- var isCurrentUserOwner = file.driveId ? (owner === null || owner === void 0 ? void 0 : owner.instance) === client.options.uri : isOwner(file._id);
86
+ var isCurrentUserOwner = file.driveId ? (owner === null || owner === void 0 ? void 0 : owner.instance) === client.options.uri : isOwner(sharingReferenceId);
64
87
 
65
88
  var showModal = function showModal() {
66
89
  if (!(0, _cozyFlags.default)('drive.new-file-viewer-ui.enabled')) {
package/dist/queries.d.ts CHANGED
@@ -13,3 +13,10 @@ export function buildFileByIdQuery(fileId: any): {
13
13
  singleDocData: boolean;
14
14
  };
15
15
  };
16
+ export function buildFolderByPathQuery(path: any): {
17
+ definition: () => import("cozy-client").QueryDefinition;
18
+ options: {
19
+ as: string;
20
+ fetchPolicy: Function;
21
+ };
22
+ };
package/dist/queries.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.buildFileByIdQuery = exports.buildContactByIdsQuery = void 0;
6
+ exports.buildFolderByPathQuery = exports.buildFileByIdQuery = exports.buildContactByIdsQuery = void 0;
7
7
 
8
8
  var _cozyClient = require("cozy-client");
9
9
 
@@ -37,4 +37,22 @@ var buildFileByIdQuery = function buildFileByIdQuery(fileId) {
37
37
  };
38
38
  };
39
39
 
40
- exports.buildFileByIdQuery = buildFileByIdQuery;
40
+ exports.buildFileByIdQuery = buildFileByIdQuery;
41
+
42
+ var buildFolderByPathQuery = function buildFolderByPathQuery(path) {
43
+ var folderPath = path || '';
44
+ return {
45
+ definition: function definition() {
46
+ return (0, _cozyClient.Q)('io.cozy.files').where({
47
+ type: 'directory',
48
+ path: folderPath
49
+ }).indexFields(['type', 'path']).limitBy(1);
50
+ },
51
+ options: {
52
+ as: "io.cozy.files/path/".concat(encodeURIComponent(folderPath)),
53
+ fetchPolicy: defaultFetchPolicy
54
+ }
55
+ };
56
+ };
57
+
58
+ exports.buildFolderByPathQuery = buildFolderByPathQuery;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-viewer",
3
- "version": "28.1.16",
3
+ "version": "28.1.18",
4
4
  "description": "Cozy-Viewer provides a component to show files in a viewer.",
5
5
  "main": "dist/index.js",
6
6
  "license": "MIT",
@@ -31,10 +31,10 @@
31
31
  "babel-preset-cozy-app": "^2.8.4",
32
32
  "cozy-client": "^60.20.0",
33
33
  "cozy-device-helper": "2.0.0",
34
- "cozy-harvest-lib": "^37.0.48",
34
+ "cozy-harvest-lib": "^37.0.50",
35
35
  "cozy-intent": "^2.31.1",
36
36
  "cozy-logger": "^1.18.1",
37
- "cozy-sharing": "^33.6.0",
37
+ "cozy-sharing": "^33.6.1",
38
38
  "cozy-ui": "^138.1.0",
39
39
  "cozy-ui-plus": "^8.0.1",
40
40
  "identity-obj-proxy": "3.0.0",
@@ -71,5 +71,5 @@
71
71
  "react-router-dom": ">=6.14.2",
72
72
  "twake-i18n": ">=0.3.0"
73
73
  },
74
- "gitHead": "fc451fb2e649fffe6bfc8f16ff812ba4c31531cf"
74
+ "gitHead": "137a6bc3214b34063dd418693c42682c0837782e"
75
75
  }
@@ -1,7 +1,7 @@
1
1
  import PropTypes from 'prop-types'
2
2
  import React from 'react'
3
3
 
4
- import { useClient } from 'cozy-client'
4
+ import { useClient, useQuery } from 'cozy-client'
5
5
  import flag from 'cozy-flags'
6
6
  import {
7
7
  getRecipientsFromSharing,
@@ -21,6 +21,7 @@ import Spinner from 'cozy-ui/transpiled/react/Spinner'
21
21
 
22
22
  import { withViewerLocales } from '../hoc/withViewerLocales'
23
23
  import { useShareModal } from '../providers/ShareModalProvider'
24
+ import { buildFolderByPathQuery } from '../queries'
24
25
 
25
26
  const Sharing = ({ file, t }) => {
26
27
  const client = useClient()
@@ -31,19 +32,38 @@ const Sharing = ({ file, t }) => {
31
32
  getSharingLink,
32
33
  allLoaded,
33
34
  getRecipients,
35
+ getSharedParentPath,
36
+ hasSharedParent,
34
37
  isOwner
35
38
  } = useSharingContext()
36
39
 
37
40
  const sharedDriveSharing = file.driveId ? getSharingById(file.driveId) : null
41
+ const sharedParentPath =
42
+ !sharedDriveSharing && file.path && hasSharedParent(file.path)
43
+ ? getSharedParentPath(file.path)
44
+ : null
45
+ const sharedParentQuery = buildFolderByPathQuery(sharedParentPath)
46
+ const { data: sharedParentFolders, fetchStatus: sharedParentFetchStatus } =
47
+ useQuery(sharedParentQuery.definition, {
48
+ ...sharedParentQuery.options,
49
+ enabled: !!sharedParentPath
50
+ })
51
+ const sharingReferenceId =
52
+ !sharedDriveSharing && sharedParentPath && sharedParentFolders?.[0]?._id
53
+ ? sharedParentFolders[0]._id
54
+ : file._id
55
+
56
+ if (sharedParentPath && sharedParentFetchStatus !== 'loaded') return null
57
+
38
58
  const recipients = sharedDriveSharing
39
59
  ? getRecipientsFromSharing(sharedDriveSharing, file._id)
40
- : getRecipients(file._id)
41
- const permissions = getDocumentPermissions(file._id)
42
- const link = getSharingLink(file._id)
60
+ : getRecipients(sharingReferenceId)
61
+ const permissions = getDocumentPermissions(sharingReferenceId)
62
+ const link = getSharingLink(sharingReferenceId)
43
63
  const owner = recipients.find(recipient => recipient.status === 'owner')
44
64
  const isCurrentUserOwner = file.driveId
45
65
  ? owner?.instance === client.options.uri
46
- : isOwner(file._id)
66
+ : isOwner(sharingReferenceId)
47
67
 
48
68
  const showModal = () => {
49
69
  if (!flag('drive.new-file-viewer-ui.enabled')) {
@@ -5,6 +5,7 @@ import Sharing from './Sharing'
5
5
 
6
6
  const mockGetRecipientsFromSharing = jest.fn()
7
7
  const mockUseClient = jest.fn()
8
+ const mockUseQuery = jest.fn()
8
9
  const mockUseShareModal = jest.fn()
9
10
  const mockUseSharingContext = jest.fn()
10
11
  const mockMemberRecipientLite = jest.fn(({ recipient, isOwner }) => (
@@ -14,7 +15,16 @@ const mockMemberRecipientLite = jest.fn(({ recipient, isOwner }) => (
14
15
  ))
15
16
 
16
17
  jest.mock('cozy-client', () => ({
17
- useClient: () => mockUseClient()
18
+ Q: () => ({
19
+ where: jest.fn().mockReturnThis(),
20
+ indexFields: jest.fn().mockReturnThis(),
21
+ limitBy: jest.fn().mockReturnThis()
22
+ }),
23
+ fetchPolicies: {
24
+ olderThan: jest.fn(() => 'fetch-policy')
25
+ },
26
+ useClient: () => mockUseClient(),
27
+ useQuery: (...args) => mockUseQuery(...args)
18
28
  }))
19
29
 
20
30
  jest.mock('cozy-flags', () => jest.fn(() => false))
@@ -40,6 +50,15 @@ jest.mock('../providers/ShareModalProvider', () => ({
40
50
 
41
51
  const file = { _id: 'file-id' }
42
52
  const sharedFile = { _id: 'shared-file-id' }
53
+ const fileInSharedFolder = {
54
+ _id: 'file-in-shared-folder-id',
55
+ path: '/shared-folder/subfolder/file.pdf'
56
+ }
57
+ const sharedFolder = {
58
+ _id: 'shared-folder-id',
59
+ path: '/shared-folder',
60
+ type: 'directory'
61
+ }
43
62
  const sharedDriveFile = { _id: 'file-id', driveId: 'drive-id' }
44
63
  const ownerRecipient = {
45
64
  index: 'recipient-0',
@@ -60,19 +79,33 @@ const sharedDriveSharing = {
60
79
  }
61
80
  }
62
81
 
63
- const setup = ({
64
- clientUri = 'http://bob.localhost:8080/',
65
- targetFile = file,
66
- userIsOwner = true
67
- } = {}) => {
82
+ const setup = (options = {}) => {
83
+ const {
84
+ clientUri = 'http://bob.localhost:8080/',
85
+ sharedParentFetchStatus = 'loaded',
86
+ targetFile = file,
87
+ userIsOwner = true
88
+ } = options
89
+ const sharedParentFolders = Object.prototype.hasOwnProperty.call(
90
+ options,
91
+ 'sharedParentFolders'
92
+ )
93
+ ? options.sharedParentFolders
94
+ : [sharedFolder]
68
95
  const getDocumentPermissions = jest
69
96
  .fn()
70
- .mockImplementation(docId => (docId === 'file-id' ? ['perm'] : []))
97
+ .mockImplementation(docId =>
98
+ docId === 'file-id' || docId === 'shared-folder-id' ? ['perm'] : []
99
+ )
71
100
  const getSharingById = jest
72
101
  .fn()
73
102
  .mockImplementation(id => (id === 'drive-id' ? sharedDriveSharing : null))
74
103
  const getRecipients = jest.fn().mockImplementation(docId => {
75
- if (docId === 'file-id' || docId === 'shared-file-id') {
104
+ if (
105
+ docId === 'file-id' ||
106
+ docId === 'shared-file-id' ||
107
+ docId === 'shared-folder-id'
108
+ ) {
76
109
  return [ownerRecipient]
77
110
  }
78
111
 
@@ -81,12 +114,26 @@ const setup = ({
81
114
  const getSharingLink = jest
82
115
  .fn()
83
116
  .mockImplementation(docId =>
84
- docId === 'file-id' ? 'http://share-link' : null
117
+ docId === 'file-id' || docId === 'shared-folder-id'
118
+ ? 'http://share-link'
119
+ : null
85
120
  )
86
121
 
122
+ mockUseQuery.mockReturnValue({
123
+ data: sharedParentFolders,
124
+ fetchStatus: sharedParentFetchStatus
125
+ })
87
126
  mockGetRecipientsFromSharing.mockReturnValue([ownerRecipient])
88
127
  mockUseClient.mockReturnValue({ options: { uri: clientUri } })
89
128
  const isOwner = jest.fn().mockReturnValue(userIsOwner)
129
+ const hasSharedParent = jest
130
+ .fn()
131
+ .mockImplementation(path => path === '/shared-folder/subfolder/file.pdf')
132
+ const getSharedParentPath = jest
133
+ .fn()
134
+ .mockImplementation(path =>
135
+ path === '/shared-folder/subfolder/file.pdf' ? '/shared-folder' : null
136
+ )
90
137
 
91
138
  mockUseShareModal.mockReturnValue({ setShowShareModal: jest.fn() })
92
139
  mockUseSharingContext.mockReturnValue({
@@ -95,6 +142,8 @@ const setup = ({
95
142
  getSharingById,
96
143
  getRecipients,
97
144
  getSharingLink,
145
+ getSharedParentPath,
146
+ hasSharedParent,
98
147
  isOwner
99
148
  })
100
149
 
@@ -146,6 +195,66 @@ describe('Sharing', () => {
146
195
  )
147
196
  })
148
197
 
198
+ it('should use shared parent recipients when a file is inside a shared folder without driveId', () => {
199
+ setup({ targetFile: fileInSharedFolder })
200
+
201
+ expect(mockUseSharingContext().getSharedParentPath).toHaveBeenCalledWith(
202
+ '/shared-folder/subfolder/file.pdf'
203
+ )
204
+ expect(mockUseSharingContext().getRecipients).toHaveBeenCalledWith(
205
+ 'shared-folder-id'
206
+ )
207
+ expect(mockUseSharingContext().getDocumentPermissions).toHaveBeenCalledWith(
208
+ 'shared-folder-id'
209
+ )
210
+ expect(mockUseSharingContext().getSharingLink).toHaveBeenCalledWith(
211
+ 'shared-folder-id'
212
+ )
213
+ expect(mockUseSharingContext().isOwner).toHaveBeenCalledWith(
214
+ 'shared-folder-id'
215
+ )
216
+ })
217
+
218
+ it('should not display sharing section while shared parent folder is loading', () => {
219
+ const { queryByText } = setup({
220
+ sharedParentFetchStatus: 'loading',
221
+ sharedParentFolders: undefined,
222
+ targetFile: fileInSharedFolder
223
+ })
224
+
225
+ expect(queryByText('Viewer.panel.sharing')).toBeNull()
226
+ })
227
+
228
+ it('should not display sharing section while shared parent folder is in pending state', () => {
229
+ const { queryByText } = setup({
230
+ sharedParentFetchStatus: 'pending',
231
+ sharedParentFolders: undefined,
232
+ targetFile: fileInSharedFolder
233
+ })
234
+
235
+ expect(queryByText('Viewer.panel.sharing')).toBeNull()
236
+ })
237
+
238
+ it('should fallback to file recipients when no shared parent folder is found', () => {
239
+ setup({
240
+ sharedParentFolders: [],
241
+ targetFile: fileInSharedFolder
242
+ })
243
+
244
+ expect(mockUseSharingContext().getRecipients).toHaveBeenCalledWith(
245
+ 'file-in-shared-folder-id'
246
+ )
247
+ expect(mockUseSharingContext().getDocumentPermissions).toHaveBeenCalledWith(
248
+ 'file-in-shared-folder-id'
249
+ )
250
+ expect(mockUseSharingContext().getSharingLink).toHaveBeenCalledWith(
251
+ 'file-in-shared-folder-id'
252
+ )
253
+ expect(mockUseSharingContext().isOwner).toHaveBeenCalledWith(
254
+ 'file-in-shared-folder-id'
255
+ )
256
+ })
257
+
149
258
  it('should not mark the owner as current user when the owner is remote', () => {
150
259
  setup({ targetFile: sharedDriveFile })
151
260
 
package/src/queries.js CHANGED
@@ -18,3 +18,19 @@ export const buildFileByIdQuery = fileId => ({
18
18
  singleDocData: true
19
19
  }
20
20
  })
21
+
22
+ export const buildFolderByPathQuery = path => {
23
+ const folderPath = path || ''
24
+
25
+ return {
26
+ definition: () =>
27
+ Q('io.cozy.files')
28
+ .where({ type: 'directory', path: folderPath })
29
+ .indexFields(['type', 'path'])
30
+ .limitBy(1),
31
+ options: {
32
+ as: `io.cozy.files/path/${encodeURIComponent(folderPath)}`,
33
+ fetchPolicy: defaultFetchPolicy
34
+ }
35
+ }
36
+ }