cozy-sharing 38.1.0 → 38.1.2

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
+ ## [38.1.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.1.1...cozy-sharing@38.1.2) (2026-09-23)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **sharing:** Reuse the ancestor drive sharing for nested documents ([698048c](https://github.com/cozy/cozy-libs/commit/698048c808fc5f244a57ced6498cb0a9f6562df0))
11
+
12
+ ## [38.1.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.1.0...cozy-sharing@38.1.1) (2026-09-21)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **sharing:** Keep full share modal for nested-folder editors ([014c848](https://github.com/cozy/cozy-libs/commit/014c848bb46699cee47eb4baf8bb7a365edaf2f1))
17
+ - **sharing:** Show editable share modal for members with write access ([cba3a13](https://github.com/cozy/cozy-libs/commit/cba3a13185d1fe088136c3a758ec94f0d3472b69))
18
+
6
19
  # [38.1.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.0.0...cozy-sharing@38.1.0) (2026-09-16)
7
20
 
8
21
  ### Features
@@ -136,8 +136,9 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
136
136
  while (1) switch (_context2.prev = _context2.next) {
137
137
  case 0:
138
138
  document = _ref2.document, recipients = _ref2.recipients, readOnlyRecipients = _ref2.readOnlyRecipients, description = _ref2.description, openSharing = _ref2.openSharing, sharedDrive = _ref2.sharedDrive;
139
- _this2$props2 = _this2.props, client = _this2$props2.client, doctype = _this2$props2.doctype;
140
- sharing = getDocumentSharing(_this2.state, document.id);
139
+ _this2$props2 = _this2.props, client = _this2$props2.client, doctype = _this2$props2.doctype; // Documents nested in a shared drive have no sharing of their own
140
+
141
+ sharing = getDocumentSharing(_this2.state, document.id) || (document.driveId ? _getSharingById(_this2.state, document.driveId) : null);
141
142
 
142
143
  if (!sharing) {
143
144
  _context2.next = 12;
@@ -793,6 +793,44 @@ describe('shared drive recipient revocation', function () {
793
793
  }
794
794
  }, _callee14);
795
795
  })));
796
+ it('adds recipients to the existing shared drive when sharing a document nested inside it', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
797
+ var nestedDocument;
798
+ return _regeneratorRuntime.wrap(function _callee15$(_context15) {
799
+ while (1) switch (_context15.prev = _context15.next) {
800
+ case 0:
801
+ sharingCol.addRecipients.mockResolvedValue({
802
+ data: SHARED_DRIVE
803
+ });
804
+ nestedDocument = {
805
+ _id: 'nested_folder_456',
806
+ id: 'nested_folder_456',
807
+ driveId: SHARED_DRIVE.id,
808
+ path: '/Shared folder/Sub folder'
809
+ };
810
+ _context15.next = 4;
811
+ return provider.share({
812
+ document: nestedDocument,
813
+ recipients: [recipient],
814
+ readOnlyRecipients: [],
815
+ description: 'Sub folder',
816
+ openSharing: false,
817
+ sharedDrive: true
818
+ });
819
+
820
+ case 4:
821
+ expect(sharingCol.create).not.toHaveBeenCalled();
822
+ expect(sharingCol.addRecipients).toHaveBeenCalledWith({
823
+ document: SHARED_DRIVE,
824
+ recipients: [recipient],
825
+ readOnlyRecipients: []
826
+ });
827
+
828
+ case 6:
829
+ case "end":
830
+ return _context15.stop();
831
+ }
832
+ }, _callee15);
833
+ })));
796
834
  });
797
835
  describe('fetchSharedDriveSharingLinks', function () {
798
836
  var PERM_DRIVE_FILE = {
@@ -817,10 +855,10 @@ describe('fetchSharedDriveSharingLinks', function () {
817
855
  id: 'file_in_drive',
818
856
  driveId: 'drive_123'
819
857
  };
820
- it('fetches shared drive sharing links by file id', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
858
+ it('fetches shared drive sharing links by file id', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
821
859
  var mockFindLinksByIds, mockClient, provider, result, permissions;
822
- return _regeneratorRuntime.wrap(function _callee15$(_context15) {
823
- while (1) switch (_context15.prev = _context15.next) {
860
+ return _regeneratorRuntime.wrap(function _callee16$(_context16) {
861
+ while (1) switch (_context16.prev = _context16.next) {
824
862
  case 0:
825
863
  mockFindLinksByIds = jest.fn().mockResolvedValue({
826
864
  data: [PERM_DRIVE_FILE]
@@ -830,11 +868,11 @@ describe('fetchSharedDriveSharingLinks', function () {
830
868
  findLinksByIds: mockFindLinksByIds
831
869
  });
832
870
  provider = setupProvider(mockClient);
833
- _context15.next = 6;
871
+ _context16.next = 6;
834
872
  return provider.fetchSharedDriveSharingLinks(driveFile);
835
873
 
836
874
  case 6:
837
- result = _context15.sent;
875
+ result = _context16.sent;
838
876
  expect(mockClient.collection).toHaveBeenCalledWith('io.cozy.permissions', {
839
877
  driveId: 'drive_123'
840
878
  });
@@ -847,14 +885,14 @@ describe('fetchSharedDriveSharingLinks', function () {
847
885
 
848
886
  case 14:
849
887
  case "end":
850
- return _context15.stop();
888
+ return _context16.stop();
851
889
  }
852
- }, _callee15);
890
+ }, _callee16);
853
891
  })));
854
- it('returns an empty array and skips the API call for non-shared-drive documents', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
892
+ it('returns an empty array and skips the API call for non-shared-drive documents', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
855
893
  var mockClient, findLinksByIds, provider, result;
856
- return _regeneratorRuntime.wrap(function _callee16$(_context16) {
857
- while (1) switch (_context16.prev = _context16.next) {
894
+ return _regeneratorRuntime.wrap(function _callee17$(_context17) {
895
+ while (1) switch (_context17.prev = _context17.next) {
858
896
  case 0:
859
897
  mockClient = createMockClient({});
860
898
  findLinksByIds = jest.fn();
@@ -863,27 +901,27 @@ describe('fetchSharedDriveSharingLinks', function () {
863
901
  });
864
902
  provider = setupProvider(mockClient);
865
903
  provider.dispatch = jest.fn();
866
- _context16.next = 7;
904
+ _context17.next = 7;
867
905
  return provider.fetchSharedDriveSharingLinks({
868
906
  _id: 'regular_file'
869
907
  });
870
908
 
871
909
  case 7:
872
- result = _context16.sent;
910
+ result = _context17.sent;
873
911
  expect(result).toEqual([]);
874
912
  expect(findLinksByIds).not.toHaveBeenCalled();
875
913
  expect(provider.dispatch).not.toHaveBeenCalled();
876
914
 
877
915
  case 11:
878
916
  case "end":
879
- return _context16.stop();
917
+ return _context17.stop();
880
918
  }
881
- }, _callee16);
919
+ }, _callee17);
882
920
  })));
883
- it('uses document.id as a fallback when _id is missing', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
921
+ it('uses document.id as a fallback when _id is missing', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() {
884
922
  var mockFindLinksByIds, mockClient, provider;
885
- return _regeneratorRuntime.wrap(function _callee17$(_context17) {
886
- while (1) switch (_context17.prev = _context17.next) {
923
+ return _regeneratorRuntime.wrap(function _callee18$(_context18) {
924
+ while (1) switch (_context18.prev = _context18.next) {
887
925
  case 0:
888
926
  mockFindLinksByIds = jest.fn().mockResolvedValue({
889
927
  data: [PERM_DRIVE_FILE]
@@ -893,7 +931,7 @@ describe('fetchSharedDriveSharingLinks', function () {
893
931
  findLinksByIds: mockFindLinksByIds
894
932
  });
895
933
  provider = setupProvider(mockClient);
896
- _context17.next = 6;
934
+ _context18.next = 6;
897
935
  return provider.fetchSharedDriveSharingLinks({
898
936
  id: 'file_in_drive',
899
937
  driveId: 'drive_123'
@@ -904,14 +942,14 @@ describe('fetchSharedDriveSharingLinks', function () {
904
942
 
905
943
  case 7:
906
944
  case "end":
907
- return _context17.stop();
945
+ return _context18.stop();
908
946
  }
909
- }, _callee17);
947
+ }, _callee18);
910
948
  })));
911
- it('does not dispatch when the response has no data', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() {
949
+ it('does not dispatch when the response has no data', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
912
950
  var mockFindLinksByIds, mockClient, provider, result;
913
- return _regeneratorRuntime.wrap(function _callee18$(_context18) {
914
- while (1) switch (_context18.prev = _context18.next) {
951
+ return _regeneratorRuntime.wrap(function _callee19$(_context19) {
952
+ while (1) switch (_context19.prev = _context19.next) {
915
953
  case 0:
916
954
  mockFindLinksByIds = jest.fn().mockResolvedValue({
917
955
  data: []
@@ -922,19 +960,19 @@ describe('fetchSharedDriveSharingLinks', function () {
922
960
  });
923
961
  provider = setupProvider(mockClient);
924
962
  provider.dispatch = jest.fn();
925
- _context18.next = 7;
963
+ _context19.next = 7;
926
964
  return provider.fetchSharedDriveSharingLinks(driveFile);
927
965
 
928
966
  case 7:
929
- result = _context18.sent;
967
+ result = _context19.sent;
930
968
  expect(result).toEqual([]);
931
969
  expect(provider.dispatch).not.toHaveBeenCalled();
932
970
 
933
971
  case 10:
934
972
  case "end":
935
- return _context18.stop();
973
+ return _context19.stop();
936
974
  }
937
- }, _callee18);
975
+ }, _callee19);
938
976
  })));
939
977
  });
940
978
  describe('shareByLink shared drive 409 recovery', function () {
@@ -960,10 +998,10 @@ describe('shareByLink shared drive 409 recovery', function () {
960
998
  id: 'file_in_drive',
961
999
  driveId: 'drive_123'
962
1000
  };
963
- it('falls back to fetching existing links when create returns 409', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
1001
+ it('falls back to fetching existing links when create returns 409', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
964
1002
  var conflict, mockCreateSharingLink, mockFindLinksByIds, mockClient, provider, resp;
965
- return _regeneratorRuntime.wrap(function _callee19$(_context19) {
966
- while (1) switch (_context19.prev = _context19.next) {
1003
+ return _regeneratorRuntime.wrap(function _callee20$(_context20) {
1004
+ while (1) switch (_context20.prev = _context20.next) {
967
1005
  case 0:
968
1006
  conflict = Object.assign(new Error('Conflict'), {
969
1007
  status: 409
@@ -978,27 +1016,27 @@ describe('shareByLink shared drive 409 recovery', function () {
978
1016
  findLinksByIds: mockFindLinksByIds
979
1017
  });
980
1018
  provider = setupProvider(mockClient);
981
- _context19.next = 8;
1019
+ _context20.next = 8;
982
1020
  return provider.shareByLink(driveFile, {
983
1021
  verbs: ['GET']
984
1022
  });
985
1023
 
986
1024
  case 8:
987
- resp = _context19.sent;
1025
+ resp = _context20.sent;
988
1026
  expect(mockCreateSharingLink).toHaveBeenCalled();
989
1027
  expect(mockFindLinksByIds).toHaveBeenCalledWith(['file_in_drive']);
990
1028
  expect(resp.data.id).toBe(PERM_DRIVE_FILE.id);
991
1029
 
992
1030
  case 12:
993
1031
  case "end":
994
- return _context19.stop();
1032
+ return _context20.stop();
995
1033
  }
996
- }, _callee19);
1034
+ }, _callee20);
997
1035
  })));
998
- it('does not duplicate existing links when 409 recovery refetches them', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
1036
+ it('does not duplicate existing links when 409 recovery refetches them', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
999
1037
  var conflict, mockCreateSharingLink, mockFindLinksByIds, mockClient, provider, resp, permissions;
1000
- return _regeneratorRuntime.wrap(function _callee20$(_context20) {
1001
- while (1) switch (_context20.prev = _context20.next) {
1038
+ return _regeneratorRuntime.wrap(function _callee21$(_context21) {
1039
+ while (1) switch (_context21.prev = _context21.next) {
1002
1040
  case 0:
1003
1041
  conflict = Object.assign(new Error('Conflict'), {
1004
1042
  status: 409
@@ -1013,13 +1051,13 @@ describe('shareByLink shared drive 409 recovery', function () {
1013
1051
  findLinksByIds: mockFindLinksByIds
1014
1052
  });
1015
1053
  provider = setupProvider(mockClient, {}, reducer(undefined, addSharingLink(PERM_DRIVE_FILE)));
1016
- _context20.next = 8;
1054
+ _context21.next = 8;
1017
1055
  return provider.shareByLink(driveFile, {
1018
1056
  verbs: ['GET']
1019
1057
  });
1020
1058
 
1021
1059
  case 8:
1022
- resp = _context20.sent;
1060
+ resp = _context21.sent;
1023
1061
  permissions = getDocumentPermissions(provider.state, 'file_in_drive');
1024
1062
  expect(resp.data.id).toBe(PERM_DRIVE_FILE.id);
1025
1063
  expect(provider.dispatch).not.toHaveBeenCalled();
@@ -1027,14 +1065,14 @@ describe('shareByLink shared drive 409 recovery', function () {
1027
1065
 
1028
1066
  case 13:
1029
1067
  case "end":
1030
- return _context20.stop();
1068
+ return _context21.stop();
1031
1069
  }
1032
- }, _callee20);
1070
+ }, _callee21);
1033
1071
  })));
1034
- it('rethrows the original error when 409 happens and no link is found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
1072
+ it('rethrows the original error when 409 happens and no link is found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
1035
1073
  var conflict, mockCreateSharingLink, mockFindLinksByIds, mockClient, provider;
1036
- return _regeneratorRuntime.wrap(function _callee21$(_context21) {
1037
- while (1) switch (_context21.prev = _context21.next) {
1074
+ return _regeneratorRuntime.wrap(function _callee22$(_context22) {
1075
+ while (1) switch (_context22.prev = _context22.next) {
1038
1076
  case 0:
1039
1077
  conflict = Object.assign(new Error('Conflict'), {
1040
1078
  status: 409
@@ -1050,16 +1088,16 @@ describe('shareByLink shared drive 409 recovery', function () {
1050
1088
  });
1051
1089
  provider = setupProvider(mockClient);
1052
1090
  provider.dispatch = jest.fn();
1053
- _context21.next = 9;
1091
+ _context22.next = 9;
1054
1092
  return expect(provider.shareByLink(driveFile, {
1055
1093
  verbs: ['GET']
1056
1094
  })).rejects.toBe(conflict);
1057
1095
 
1058
1096
  case 9:
1059
1097
  case "end":
1060
- return _context21.stop();
1098
+ return _context22.stop();
1061
1099
  }
1062
- }, _callee21);
1100
+ }, _callee22);
1063
1101
  })));
1064
1102
  });
1065
1103
  describe('updateSharingMemberType', function () {
@@ -1102,25 +1140,12 @@ describe('updateSharingMemberType', function () {
1102
1140
  })
1103
1141
  };
1104
1142
  });
1105
- it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
1106
- return _regeneratorRuntime.wrap(function _callee22$(_context22) {
1107
- while (1) switch (_context22.prev = _context22.next) {
1108
- case 0:
1109
- _context22.next = 2;
1110
- return expect(instance.updateSharingMemberType('unknown-id', 1, 'one-way')).rejects.toThrow('Sharing not found');
1111
-
1112
- case 2:
1113
- case "end":
1114
- return _context22.stop();
1115
- }
1116
- }, _callee22);
1117
- })));
1118
- it('should throw when member is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
1143
+ it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
1119
1144
  return _regeneratorRuntime.wrap(function _callee23$(_context23) {
1120
1145
  while (1) switch (_context23.prev = _context23.next) {
1121
1146
  case 0:
1122
1147
  _context23.next = 2;
1123
- return expect(instance.updateSharingMemberType('sharing-123', 99, 'one-way')).rejects.toThrow('Member not found');
1148
+ return expect(instance.updateSharingMemberType('unknown-id', 1, 'one-way')).rejects.toThrow('Sharing not found');
1124
1149
 
1125
1150
  case 2:
1126
1151
  case "end":
@@ -1128,33 +1153,29 @@ describe('updateSharingMemberType', function () {
1128
1153
  }
1129
1154
  }, _callee23);
1130
1155
  })));
1131
- it('should call setReadOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24() {
1156
+ it('should throw when member is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24() {
1132
1157
  return _regeneratorRuntime.wrap(function _callee24$(_context24) {
1133
1158
  while (1) switch (_context24.prev = _context24.next) {
1134
1159
  case 0:
1135
1160
  _context24.next = 2;
1136
- return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
1161
+ return expect(instance.updateSharingMemberType('sharing-123', 99, 'one-way')).rejects.toThrow('Member not found');
1137
1162
 
1138
1163
  case 2:
1139
- expect(instance.sharingCol.setReadOnly).toHaveBeenCalledWith(mockSharing, 1);
1140
- expect(instance.sharingCol.setReadWrite).not.toHaveBeenCalled();
1141
-
1142
- case 4:
1143
1164
  case "end":
1144
1165
  return _context24.stop();
1145
1166
  }
1146
1167
  }, _callee24);
1147
1168
  })));
1148
- it('should call setReadWrite when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee25() {
1169
+ it('should call setReadOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee25() {
1149
1170
  return _regeneratorRuntime.wrap(function _callee25$(_context25) {
1150
1171
  while (1) switch (_context25.prev = _context25.next) {
1151
1172
  case 0:
1152
1173
  _context25.next = 2;
1153
- return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
1174
+ return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
1154
1175
 
1155
1176
  case 2:
1156
- expect(instance.sharingCol.setReadWrite).toHaveBeenCalledWith(mockSharing, 1);
1157
- expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled();
1177
+ expect(instance.sharingCol.setReadOnly).toHaveBeenCalledWith(mockSharing, 1);
1178
+ expect(instance.sharingCol.setReadWrite).not.toHaveBeenCalled();
1158
1179
 
1159
1180
  case 4:
1160
1181
  case "end":
@@ -1162,37 +1183,29 @@ describe('updateSharingMemberType', function () {
1162
1183
  }
1163
1184
  }, _callee25);
1164
1185
  })));
1165
- it('should optimistically dispatch a state update when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee26() {
1186
+ it('should call setReadWrite when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee26() {
1166
1187
  return _regeneratorRuntime.wrap(function _callee26$(_context26) {
1167
1188
  while (1) switch (_context26.prev = _context26.next) {
1168
1189
  case 0:
1169
1190
  _context26.next = 2;
1170
- return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
1191
+ return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
1171
1192
 
1172
1193
  case 2:
1173
- expect(instance.dispatch).toHaveBeenCalledWith({
1174
- type: 'UPDATE_SHARING',
1175
- sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
1176
- attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
1177
- members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
1178
- read_only: true
1179
- })]
1180
- })
1181
- })
1182
- });
1194
+ expect(instance.sharingCol.setReadWrite).toHaveBeenCalledWith(mockSharing, 1);
1195
+ expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled();
1183
1196
 
1184
- case 3:
1197
+ case 4:
1185
1198
  case "end":
1186
1199
  return _context26.stop();
1187
1200
  }
1188
1201
  }, _callee26);
1189
1202
  })));
1190
- it('should optimistically dispatch a state update when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee27() {
1203
+ it('should optimistically dispatch a state update when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee27() {
1191
1204
  return _regeneratorRuntime.wrap(function _callee27$(_context27) {
1192
1205
  while (1) switch (_context27.prev = _context27.next) {
1193
1206
  case 0:
1194
1207
  _context27.next = 2;
1195
- return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
1208
+ return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
1196
1209
 
1197
1210
  case 2:
1198
1211
  expect(instance.dispatch).toHaveBeenCalledWith({
@@ -1200,7 +1213,7 @@ describe('updateSharingMemberType', function () {
1200
1213
  sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
1201
1214
  attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
1202
1215
  members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
1203
- read_only: false
1216
+ read_only: true
1204
1217
  })]
1205
1218
  })
1206
1219
  })
@@ -1212,13 +1225,24 @@ describe('updateSharingMemberType', function () {
1212
1225
  }
1213
1226
  }, _callee27);
1214
1227
  })));
1215
- it('should rethrow error if setReadOnly fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee28() {
1228
+ it('should optimistically dispatch a state update when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee28() {
1216
1229
  return _regeneratorRuntime.wrap(function _callee28$(_context28) {
1217
1230
  while (1) switch (_context28.prev = _context28.next) {
1218
1231
  case 0:
1219
- instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
1220
- _context28.next = 3;
1221
- return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
1232
+ _context28.next = 2;
1233
+ return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
1234
+
1235
+ case 2:
1236
+ expect(instance.dispatch).toHaveBeenCalledWith({
1237
+ type: 'UPDATE_SHARING',
1238
+ sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
1239
+ attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
1240
+ members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
1241
+ read_only: false
1242
+ })]
1243
+ })
1244
+ })
1245
+ });
1222
1246
 
1223
1247
  case 3:
1224
1248
  case "end":
@@ -1226,13 +1250,13 @@ describe('updateSharingMemberType', function () {
1226
1250
  }
1227
1251
  }, _callee28);
1228
1252
  })));
1229
- it('should rethrow error if setReadWrite fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee29() {
1253
+ it('should rethrow error if setReadOnly fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee29() {
1230
1254
  return _regeneratorRuntime.wrap(function _callee29$(_context29) {
1231
1255
  while (1) switch (_context29.prev = _context29.next) {
1232
1256
  case 0:
1233
- instance.sharingCol.setReadWrite.mockRejectedValue(new Error('Network error'));
1257
+ instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
1234
1258
  _context29.next = 3;
1235
- return expect(instance.updateSharingMemberType('sharing-123', 1, 'two-way')).rejects.toThrow('Network error');
1259
+ return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
1236
1260
 
1237
1261
  case 3:
1238
1262
  case "end":
@@ -1240,12 +1264,26 @@ describe('updateSharingMemberType', function () {
1240
1264
  }
1241
1265
  }, _callee29);
1242
1266
  })));
1243
- it('should rollback optimistic state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee30() {
1267
+ it('should rethrow error if setReadWrite fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee30() {
1244
1268
  return _regeneratorRuntime.wrap(function _callee30$(_context30) {
1245
1269
  while (1) switch (_context30.prev = _context30.next) {
1246
1270
  case 0:
1247
- instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
1271
+ instance.sharingCol.setReadWrite.mockRejectedValue(new Error('Network error'));
1248
1272
  _context30.next = 3;
1273
+ return expect(instance.updateSharingMemberType('sharing-123', 1, 'two-way')).rejects.toThrow('Network error');
1274
+
1275
+ case 3:
1276
+ case "end":
1277
+ return _context30.stop();
1278
+ }
1279
+ }, _callee30);
1280
+ })));
1281
+ it('should rollback optimistic state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee31() {
1282
+ return _regeneratorRuntime.wrap(function _callee31$(_context31) {
1283
+ while (1) switch (_context31.prev = _context31.next) {
1284
+ case 0:
1285
+ instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
1286
+ _context31.next = 3;
1249
1287
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
1250
1288
 
1251
1289
  case 3:
@@ -1284,14 +1322,14 @@ describe('updateSharingMemberType', function () {
1284
1322
 
1285
1323
  case 7:
1286
1324
  case "end":
1287
- return _context30.stop();
1325
+ return _context31.stop();
1288
1326
  }
1289
- }, _callee30);
1327
+ }, _callee31);
1290
1328
  })));
1291
- it('should preserve realtime changes when rolling back an API failure', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee32() {
1329
+ it('should preserve realtime changes when rolling back an API failure', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee33() {
1292
1330
  var realtimeSharing;
1293
- return _regeneratorRuntime.wrap(function _callee32$(_context32) {
1294
- while (1) switch (_context32.prev = _context32.next) {
1331
+ return _regeneratorRuntime.wrap(function _callee33$(_context33) {
1332
+ while (1) switch (_context33.prev = _context33.next) {
1295
1333
  case 0:
1296
1334
  realtimeSharing = _objectSpread(_objectSpread({}, mockSharing), {}, {
1297
1335
  attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
@@ -1303,9 +1341,9 @@ describe('updateSharingMemberType', function () {
1303
1341
  })]
1304
1342
  })
1305
1343
  });
1306
- instance.sharingCol.setReadOnly.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee31() {
1307
- return _regeneratorRuntime.wrap(function _callee31$(_context31) {
1308
- while (1) switch (_context31.prev = _context31.next) {
1344
+ instance.sharingCol.setReadOnly.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee32() {
1345
+ return _regeneratorRuntime.wrap(function _callee32$(_context32) {
1346
+ while (1) switch (_context32.prev = _context32.next) {
1309
1347
  case 0:
1310
1348
  instance.state = _objectSpread(_objectSpread({}, instance.state), {}, {
1311
1349
  sharings: instance.state.sharings.map(function (sharing) {
@@ -1316,11 +1354,11 @@ describe('updateSharingMemberType', function () {
1316
1354
 
1317
1355
  case 2:
1318
1356
  case "end":
1319
- return _context31.stop();
1357
+ return _context32.stop();
1320
1358
  }
1321
- }, _callee31);
1359
+ }, _callee32);
1322
1360
  })));
1323
- _context32.next = 4;
1361
+ _context33.next = 4;
1324
1362
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
1325
1363
 
1326
1364
  case 4:
@@ -1349,9 +1387,9 @@ describe('updateSharingMemberType', function () {
1349
1387
 
1350
1388
  case 7:
1351
1389
  case "end":
1352
- return _context32.stop();
1390
+ return _context33.stop();
1353
1391
  }
1354
- }, _callee32);
1392
+ }, _callee33);
1355
1393
  })));
1356
1394
  }); // TODO Convert with react-testing-library
1357
1395
  // describe('hasWriteAccess', () => {
@@ -64,6 +64,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
64
64
 
65
65
  var _useSharingContext = useSharingContext(),
66
66
  canReshare = _useSharingContext.canReshare,
67
+ hasWriteAccess = _useSharingContext.hasWriteAccess,
67
68
  share = _useSharingContext.share,
68
69
  getSharingById = _useSharingContext.getSharingById,
69
70
  getSharingLink = _useSharingContext.getSharingLink,
@@ -125,7 +126,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
125
126
  }));
126
127
  var isInsideSharedDrive = Boolean((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.driveId) && !isSharedDriveRoot);
127
128
  var isCurrentUserOwner = existingDocument !== null && existingDocument !== void 0 && existingDocument.driveId ? Boolean(sharedDriveSharing === null || sharedDriveSharing === void 0 || (_sharedDriveSharing$a2 = sharedDriveSharing.attributes) === null || _sharedDriveSharing$a2 === void 0 ? void 0 : _sharedDriveSharing$a2.owner) : documentId ? isOwner(documentId) : false;
128
- var isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner;
129
+ var isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner && !hasWriteAccess(documentId, existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.driveId);
129
130
  var canManageSharing = isCurrentUserOwner || (documentId ? canReshare(documentId) : false);
130
131
 
131
132
  var handleRevokeSelf = /*#__PURE__*/function () {
@@ -318,7 +319,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
318
319
  className: "u-ph-2"
319
320
  }, /*#__PURE__*/React.createElement(AntivirusAlert, {
320
321
  document: isSending ? frozenDoc : existingDocument
321
- }), isInsideSharedDrive ? /*#__PURE__*/React.createElement("div", {
322
+ }), isMemberReadOnly ? /*#__PURE__*/React.createElement("div", {
322
323
  className: styles['share-byemail-onlybylink']
323
324
  }, t('Files.share.shareByEmail.onlyByLink', {
324
325
  type: t("Files.share.shareByEmail.type.".concat((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.type) === 'file' ? 'file' : 'folder'))
@@ -354,7 +355,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
354
355
  documentType: "Files",
355
356
  showGenerateLinkButton: showGenerateLinkButton,
356
357
  autoOpenShareRestriction: autoOpenShareRestriction
357
- }), !isInsideSharedDrive && /*#__PURE__*/React.createElement(Button, {
358
+ }), !isMemberReadOnly && /*#__PURE__*/React.createElement(Button, {
358
359
  variant: "primary",
359
360
  label: t('FederatedFolder.share'),
360
361
  busy: isSending,