cozy-sharing 37.5.4 → 37.6.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 +11 -0
- package/README.md +3 -2
- package/dist/SharingProvider.js +270 -196
- package/dist/SharingProvider.spec.js +34 -4
- package/dist/components/FederatedFolder/FederatedFolderModal.js +61 -82
- package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +19 -8
- package/dist/components/ShareDialogCozyToCozy.js +21 -108
- package/dist/components/ShareModal/EditableSharingModal.js +44 -8
- package/dist/components/ShareModal/ShareModal.js +2 -2
- package/dist/components/ShareModal.js +2 -2
- package/dist/components/SharingContent.js +92 -0
- package/dist/helpers/effectiveRecipients.js +40 -0
- package/dist/helpers/effectiveRecipients.spec.js +134 -0
- package/dist/state.js +68 -1
- package/dist/state.spec.js +2 -1
- package/package.json +3 -3
- package/src/SharingProvider.jsx +61 -1
- package/src/SharingProvider.spec.jsx +28 -2
- package/src/components/FederatedFolder/FederatedFolderModal.jsx +50 -65
- package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +16 -8
- package/src/components/ShareDialogCozyToCozy.jsx +1 -87
- package/src/components/ShareModal/EditableSharingModal.jsx +61 -8
- package/src/components/ShareModal/ShareModal.jsx +2 -2
- package/src/components/ShareModal.jsx +2 -2
- package/src/components/SharingContent.jsx +94 -0
- package/src/helpers/effectiveRecipients.js +34 -0
- package/src/helpers/effectiveRecipients.spec.js +125 -0
- package/src/state.js +52 -1
- package/src/state.spec.js +2 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the most relevant source for a recipient: prefer the target's own
|
|
3
|
+
* share ("self"), fall back to the first ancestor source.
|
|
4
|
+
* Needed because revoke/RO-RW actions target a specific sharing+member index.
|
|
5
|
+
*/
|
|
6
|
+
const getBestSource = effectiveRecipient => {
|
|
7
|
+
const sources = effectiveRecipient.sources || []
|
|
8
|
+
return sources.find(s => s.kind === 'self') || sources[0]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a view object for MemberRecipient/MemberRecipientPermissions.
|
|
13
|
+
* Extracts sharingId/memberIndex/avatarPath/type from the source so
|
|
14
|
+
* existing components work without reading sources[] directly.
|
|
15
|
+
* Does not spread the backend data — only the fields needed for rendering.
|
|
16
|
+
*/
|
|
17
|
+
const mapToRecipient = (effectiveRecipient, source) => ({
|
|
18
|
+
name: effectiveRecipient.name,
|
|
19
|
+
email: effectiveRecipient.email,
|
|
20
|
+
instance: effectiveRecipient.instance,
|
|
21
|
+
status: effectiveRecipient.status,
|
|
22
|
+
read_only: effectiveRecipient.read_only,
|
|
23
|
+
index: `effective-${source.sharing_id}-${source.member_index}`,
|
|
24
|
+
sharingId: source.sharing_id,
|
|
25
|
+
memberIndex: source.member_index,
|
|
26
|
+
avatarPath: `/sharings/${source.sharing_id}/recipients/${source.member_index}/avatar`,
|
|
27
|
+
type: effectiveRecipient.read_only ? 'one-way' : 'two-way'
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const mapEffectiveRecipients = effectiveRecipients =>
|
|
31
|
+
(effectiveRecipients || []).map(recipient => {
|
|
32
|
+
const source = getBestSource(recipient)
|
|
33
|
+
return source ? mapToRecipient(recipient, source) : recipient
|
|
34
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { mapEffectiveRecipients } from './effectiveRecipients'
|
|
2
|
+
|
|
3
|
+
const makeAncestorSource = (rootName = 'Parent Folder') => ({
|
|
4
|
+
sharing_id: 'sharing-1',
|
|
5
|
+
root_id: 'root-1',
|
|
6
|
+
root_name: rootName,
|
|
7
|
+
kind: 'ancestor',
|
|
8
|
+
member_index: 1,
|
|
9
|
+
read_only: false,
|
|
10
|
+
manageable: false
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const makeSelfSource = (opts = {}) => ({
|
|
14
|
+
sharing_id: opts.sharing_id || 'sharing-2',
|
|
15
|
+
root_id: 'root-2',
|
|
16
|
+
root_name: 'Child Folder',
|
|
17
|
+
kind: 'self',
|
|
18
|
+
member_index: opts.memberIndex ?? 1,
|
|
19
|
+
read_only: opts.readOnly ?? false,
|
|
20
|
+
manageable: opts.manageable ?? true
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const makeRecipient = (overrides = {}) => ({
|
|
24
|
+
id: 'recip-1',
|
|
25
|
+
name: 'Alice',
|
|
26
|
+
email: 'alice@example.com',
|
|
27
|
+
instance: 'https://alice.mycozy.cloud',
|
|
28
|
+
status: 'ready',
|
|
29
|
+
read_only: false,
|
|
30
|
+
can_edit_here: false,
|
|
31
|
+
sources: overrides.sources || [],
|
|
32
|
+
...overrides
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('mapEffectiveRecipients', () => {
|
|
36
|
+
it('returns empty array for empty input', () => {
|
|
37
|
+
expect(mapEffectiveRecipients([])).toEqual([])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('returns empty array for null or undefined', () => {
|
|
41
|
+
expect(mapEffectiveRecipients(null)).toEqual([])
|
|
42
|
+
expect(mapEffectiveRecipients(undefined)).toEqual([])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('maps a recipient with a self source using the self source', () => {
|
|
46
|
+
const recipient = makeRecipient({
|
|
47
|
+
sources: [makeSelfSource()]
|
|
48
|
+
})
|
|
49
|
+
const result = mapEffectiveRecipients([recipient])
|
|
50
|
+
|
|
51
|
+
expect(result).toHaveLength(1)
|
|
52
|
+
expect(result[0].sharingId).toBe('sharing-2')
|
|
53
|
+
expect(result[0].memberIndex).toBe(1)
|
|
54
|
+
expect(result[0].avatarPath).toBe('/sharings/sharing-2/recipients/1/avatar')
|
|
55
|
+
expect(result[0].type).toBe('two-way')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('maps a recipient with only ancestor sources using the ancestor source', () => {
|
|
59
|
+
const recipient = makeRecipient({
|
|
60
|
+
sources: [makeAncestorSource('Shared Parent')]
|
|
61
|
+
})
|
|
62
|
+
const result = mapEffectiveRecipients([recipient])
|
|
63
|
+
|
|
64
|
+
expect(result).toHaveLength(1)
|
|
65
|
+
expect(result[0].sharingId).toBe('sharing-1')
|
|
66
|
+
expect(result[0].memberIndex).toBe(1)
|
|
67
|
+
expect(result[0].name).toBe('Alice')
|
|
68
|
+
expect(result[0].email).toBe('alice@example.com')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('maps an owner (self source, index 0) correctly', () => {
|
|
72
|
+
const recipient = makeRecipient({
|
|
73
|
+
status: 'owner',
|
|
74
|
+
read_only: true,
|
|
75
|
+
sources: [makeSelfSource({ memberIndex: 0, manageable: false })]
|
|
76
|
+
})
|
|
77
|
+
const result = mapEffectiveRecipients([recipient])
|
|
78
|
+
|
|
79
|
+
expect(result).toHaveLength(1)
|
|
80
|
+
expect(result[0].status).toBe('owner')
|
|
81
|
+
expect(result[0].type).toBe('one-way')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('maps an owner with only ancestor sources correctly', () => {
|
|
85
|
+
const recipient = makeRecipient({
|
|
86
|
+
status: 'owner',
|
|
87
|
+
sources: [makeAncestorSource('Parent Folder')]
|
|
88
|
+
})
|
|
89
|
+
const result = mapEffectiveRecipients([recipient])
|
|
90
|
+
|
|
91
|
+
expect(result).toHaveLength(1)
|
|
92
|
+
expect(result[0].status).toBe('owner')
|
|
93
|
+
expect(result[0].sharingId).toBe('sharing-1')
|
|
94
|
+
expect(result[0].memberIndex).toBe(1)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('sets type to one-way for read_only recipients', () => {
|
|
98
|
+
const recipient = makeRecipient({
|
|
99
|
+
read_only: true,
|
|
100
|
+
sources: [makeSelfSource({ readOnly: true })]
|
|
101
|
+
})
|
|
102
|
+
const result = mapEffectiveRecipients([recipient])
|
|
103
|
+
expect(result[0].type).toBe('one-way')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('handles all recipients in the same array regardless of source kind', () => {
|
|
107
|
+
const selfRecipient = makeRecipient({
|
|
108
|
+
id: 'self-1',
|
|
109
|
+
name: 'Bob',
|
|
110
|
+
email: 'bob@example.com',
|
|
111
|
+
sources: [makeSelfSource()]
|
|
112
|
+
})
|
|
113
|
+
const ancestorRecipient = makeRecipient({
|
|
114
|
+
id: 'ancestor-1',
|
|
115
|
+
name: 'Carol',
|
|
116
|
+
email: 'carol@example.com',
|
|
117
|
+
sources: [makeAncestorSource('Root Folder')]
|
|
118
|
+
})
|
|
119
|
+
const result = mapEffectiveRecipients([selfRecipient, ancestorRecipient])
|
|
120
|
+
|
|
121
|
+
expect(result).toHaveLength(2)
|
|
122
|
+
expect(result[0].sharingId).toBe('sharing-2')
|
|
123
|
+
expect(result[1].sharingId).toBe('sharing-1')
|
|
124
|
+
})
|
|
125
|
+
})
|
package/src/state.js
CHANGED
|
@@ -13,6 +13,9 @@ const ADD_SHARING_LINK = 'ADD_SHARING_LINK'
|
|
|
13
13
|
const UPDATE_SHARING_LINK = 'UPDATE_SHARING_LINK'
|
|
14
14
|
const REVOKE_SHARING_LINK = 'REVOKE_SHARING_LINK'
|
|
15
15
|
const RECEIVE_PATHS = 'RECEIVE_PATHS'
|
|
16
|
+
const RECEIVE_EFFECTIVE_RECIPIENTS = 'RECEIVE_EFFECTIVE_RECIPIENTS'
|
|
17
|
+
const UPDATE_EFFECTIVE_RECIPIENT = 'UPDATE_EFFECTIVE_RECIPIENT'
|
|
18
|
+
const INVALIDATE_EFFECTIVE_RECIPIENTS = 'INVALIDATE_EFFECTIVE_RECIPIENTS'
|
|
16
19
|
export const SHARING_TYPE = {
|
|
17
20
|
TWO_WAY: 'two-way',
|
|
18
21
|
ONE_WAY: 'one-way'
|
|
@@ -101,6 +104,21 @@ export const revokeSharingLink = permissions => ({
|
|
|
101
104
|
permissions
|
|
102
105
|
})
|
|
103
106
|
export const receivePaths = paths => ({ type: RECEIVE_PATHS, paths })
|
|
107
|
+
export const receiveEffectiveRecipients = (docId, recipients) => ({
|
|
108
|
+
type: RECEIVE_EFFECTIVE_RECIPIENTS,
|
|
109
|
+
docId,
|
|
110
|
+
recipients
|
|
111
|
+
})
|
|
112
|
+
export const updateEffectiveRecipient = (sharingId, memberIndex, readOnly) => ({
|
|
113
|
+
type: UPDATE_EFFECTIVE_RECIPIENT,
|
|
114
|
+
sharingId,
|
|
115
|
+
memberIndex,
|
|
116
|
+
readOnly
|
|
117
|
+
})
|
|
118
|
+
export const invalidateEffectiveRecipients = docId => ({
|
|
119
|
+
type: INVALIDATE_EFFECTIVE_RECIPIENTS,
|
|
120
|
+
docId
|
|
121
|
+
})
|
|
104
122
|
export const matchingInstanceName =
|
|
105
123
|
(instanceUri = '') =>
|
|
106
124
|
shareMember =>
|
|
@@ -281,11 +299,41 @@ const sharedPaths = (state = [], action) => {
|
|
|
281
299
|
}
|
|
282
300
|
}
|
|
283
301
|
|
|
302
|
+
const effectiveRecipients = (state = {}, action) => {
|
|
303
|
+
switch (action.type) {
|
|
304
|
+
case RECEIVE_EFFECTIVE_RECIPIENTS:
|
|
305
|
+
return { ...state, [action.docId]: action.recipients }
|
|
306
|
+
case UPDATE_EFFECTIVE_RECIPIENT:
|
|
307
|
+
return Object.fromEntries(
|
|
308
|
+
Object.entries(state).map(([docId, recipients]) => [
|
|
309
|
+
docId,
|
|
310
|
+
recipients.map(r =>
|
|
311
|
+
r.sharingId === action.sharingId &&
|
|
312
|
+
r.memberIndex === action.memberIndex
|
|
313
|
+
? {
|
|
314
|
+
...r,
|
|
315
|
+
read_only: action.readOnly,
|
|
316
|
+
type: action.readOnly ? 'one-way' : 'two-way'
|
|
317
|
+
}
|
|
318
|
+
: r
|
|
319
|
+
)
|
|
320
|
+
])
|
|
321
|
+
)
|
|
322
|
+
case INVALIDATE_EFFECTIVE_RECIPIENTS: {
|
|
323
|
+
const { [action.docId]: _, ...rest } = state
|
|
324
|
+
return rest
|
|
325
|
+
}
|
|
326
|
+
default:
|
|
327
|
+
return state
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
284
331
|
const reducer = (state = {}, action = {}) => ({
|
|
285
332
|
byDocId: byDocId(state.byDocId, action),
|
|
286
333
|
sharings: sharings(state.sharings, action),
|
|
287
334
|
permissions: permissions(state.permissions, action),
|
|
288
|
-
sharedPaths: sharedPaths(state.sharedPaths, action)
|
|
335
|
+
sharedPaths: sharedPaths(state.sharedPaths, action),
|
|
336
|
+
effectiveRecipients: effectiveRecipients(state.effectiveRecipients, action)
|
|
289
337
|
})
|
|
290
338
|
export default reducer
|
|
291
339
|
|
|
@@ -527,6 +575,9 @@ export const getSharedParentPath = (state, documentPath) => {
|
|
|
527
575
|
return null
|
|
528
576
|
}
|
|
529
577
|
|
|
578
|
+
export const getEffectiveRecipients = (state, docId) =>
|
|
579
|
+
state.effectiveRecipients?.[docId] || []
|
|
580
|
+
|
|
530
581
|
// helpers
|
|
531
582
|
export const getSharedDocIds = doc =>
|
|
532
583
|
doc.type === 'io.cozy.sharings'
|