cozy-sharing 28.2.0 → 28.3.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 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.3.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.2.1...cozy-sharing@28.3.0) (2026-01-26)
7
+
8
+ ### Features
9
+
10
+ - Generate and copy folder link in one click ([7b806da](https://github.com/cozy/cozy-libs/commit/7b806dae2564c45d1a469150832ebed3ac47235a))
11
+
12
+ ## [28.2.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.2.0...cozy-sharing@28.2.1) (2026-01-15)
13
+
14
+ **Note:** Version bump only for package cozy-sharing
15
+
6
16
  # [28.2.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.1.4...cozy-sharing@28.2.0) (2026-01-14)
7
17
 
8
18
  ### Features
@@ -2,8 +2,9 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import _regeneratorRuntime from "@babel/runtime/regenerator";
4
4
  import PropTypes from 'prop-types';
5
- import React, { useReducer } from 'react';
5
+ import React, { useReducer, useState } from 'react';
6
6
  import { useI18n } from 'twake-i18n';
7
+ import { useClient } from 'cozy-client';
7
8
  import Button from 'cozy-ui/transpiled/react/Buttons';
8
9
  import Icon from 'cozy-ui/transpiled/react/Icon';
9
10
  import CopyIcon from 'cozy-ui/transpiled/react/Icons/Copy';
@@ -11,9 +12,12 @@ import LinkIcon from 'cozy-ui/transpiled/react/Icons/Link';
11
12
  import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert';
12
13
  import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints';
13
14
  import { ShareRestrictionModal } from './ShareRestrictionModal/ShareRestrictionModal';
14
- import { copyToClipboard } from './ShareRestrictionModal/helpers';
15
+ import { copyToClipboard, createSharingLink } from './ShareRestrictionModal/helpers';
16
+ import { useSharingContext } from '../hooks/useSharingContext';
15
17
 
16
18
  var ShareByLink = function ShareByLink(_ref) {
19
+ var _navigator;
20
+
17
21
  var link = _ref.link,
18
22
  document = _ref.document,
19
23
  documentType = _ref.documentType;
@@ -27,8 +31,19 @@ var ShareByLink = function ShareByLink(_ref) {
27
31
  var _useAlert = useAlert(),
28
32
  showAlert = _useAlert.showAlert;
29
33
 
30
- var showCopyAndSendButtons = link && isMobile && navigator.share;
31
- var showOnlyCopyButton = link && !isMobile || link && isMobile && !navigator.share;
34
+ var client = useClient();
35
+
36
+ var _useSharingContext = useSharingContext(),
37
+ shareByLink = _useSharingContext.shareByLink;
38
+
39
+ var _useState = useState(false),
40
+ _useState2 = _slicedToArray(_useState, 2),
41
+ isGenerating = _useState2[0],
42
+ setIsGenerating = _useState2[1];
43
+
44
+ var canShare = typeof ((_navigator = navigator) === null || _navigator === void 0 ? void 0 : _navigator.share) === 'function';
45
+ var showCopyAndSendButtons = isMobile && canShare;
46
+ var showOnlyCopyButton = !isMobile || !canShare;
32
47
 
33
48
  var _useReducer = useReducer(function (state) {
34
49
  return !state;
@@ -37,55 +52,158 @@ var ShareByLink = function ShareByLink(_ref) {
37
52
  isEditDialogOpen = _useReducer2[0],
38
53
  toggleEditDialogOpen = _useReducer2[1];
39
54
 
40
- var copyLinkToClipboard = /*#__PURE__*/function () {
55
+ var handleGenerateLink = /*#__PURE__*/function () {
41
56
  var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
42
57
  return _regeneratorRuntime.wrap(function _callee$(_context) {
43
58
  while (1) switch (_context.prev = _context.next) {
44
59
  case 0:
45
- _context.next = 2;
46
- return copyToClipboard(link, {
60
+ setIsGenerating(true);
61
+ _context.prev = 1;
62
+ _context.next = 4;
63
+ return createSharingLink({
64
+ client: client,
65
+ file: document,
66
+ documentType: documentType,
67
+ shareByLink: shareByLink,
47
68
  t: t,
48
- showAlert: showAlert
69
+ showAlert: showAlert,
70
+ password: '',
71
+ ttl: undefined,
72
+ editingRights: 'readOnly'
49
73
  });
50
74
 
51
- case 2:
75
+ case 4:
76
+ return _context.abrupt("return", _context.sent);
77
+
78
+ case 7:
79
+ _context.prev = 7;
80
+ _context.t0 = _context["catch"](1);
81
+ return _context.abrupt("return", null);
82
+
83
+ case 10:
84
+ _context.prev = 10;
85
+ setIsGenerating(false);
86
+ return _context.finish(10);
87
+
88
+ case 13:
52
89
  case "end":
53
90
  return _context.stop();
54
91
  }
55
- }, _callee);
92
+ }, _callee, null, [[1, 7, 10, 13]]);
56
93
  }));
57
94
 
58
- return function copyLinkToClipboard() {
95
+ return function handleGenerateLink() {
59
96
  return _ref2.apply(this, arguments);
60
97
  };
61
98
  }();
62
99
 
63
- var shareLink = /*#__PURE__*/function () {
100
+ var generateLinkAndCopyLinkToClipboard = /*#__PURE__*/function () {
64
101
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
65
- var shareData;
102
+ var linkToCopy;
66
103
  return _regeneratorRuntime.wrap(function _callee2$(_context2) {
67
104
  while (1) switch (_context2.prev = _context2.next) {
68
105
  case 0:
69
- _context2.prev = 0;
106
+ linkToCopy = link;
107
+
108
+ if (linkToCopy) {
109
+ _context2.next = 5;
110
+ break;
111
+ }
112
+
113
+ _context2.next = 4;
114
+ return handleGenerateLink();
115
+
116
+ case 4:
117
+ linkToCopy = _context2.sent;
118
+
119
+ case 5:
120
+ if (!linkToCopy) {
121
+ _context2.next = 10;
122
+ break;
123
+ }
124
+
125
+ _context2.next = 8;
126
+ return copyToClipboard(linkToCopy, {
127
+ t: t,
128
+ showAlert: showAlert
129
+ });
130
+
131
+ case 8:
132
+ _context2.next = 11;
133
+ break;
134
+
135
+ case 10:
136
+ showAlert({
137
+ message: t("".concat(documentType, ".share.error.generic")),
138
+ severity: 'error',
139
+ variant: 'filled'
140
+ });
141
+
142
+ case 11:
143
+ case "end":
144
+ return _context2.stop();
145
+ }
146
+ }, _callee2);
147
+ }));
148
+
149
+ return function generateLinkAndCopyLinkToClipboard() {
150
+ return _ref3.apply(this, arguments);
151
+ };
152
+ }();
153
+
154
+ var shareLink = /*#__PURE__*/function () {
155
+ var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
156
+ var linkToShare, shareData;
157
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
158
+ while (1) switch (_context3.prev = _context3.next) {
159
+ case 0:
160
+ linkToShare = link;
161
+
162
+ if (linkToShare) {
163
+ _context3.next = 5;
164
+ break;
165
+ }
166
+
167
+ _context3.next = 4;
168
+ return handleGenerateLink();
169
+
170
+ case 4:
171
+ linkToShare = _context3.sent;
172
+
173
+ case 5:
174
+ if (linkToShare) {
175
+ _context3.next = 8;
176
+ break;
177
+ }
178
+
179
+ showAlert({
180
+ message: t("".concat(documentType, ".share.error.generic")),
181
+ severity: 'error',
182
+ variant: 'filled'
183
+ });
184
+ return _context3.abrupt("return");
185
+
186
+ case 8:
187
+ _context3.prev = 8;
70
188
  shareData = {
71
189
  text: t("".concat(documentType, ".share.shareByLink.shareDescription"), {
72
190
  name: document.name || ''
73
191
  }),
74
- url: link
192
+ url: linkToShare
75
193
  };
76
- _context2.next = 4;
194
+ _context3.next = 12;
77
195
  return navigator.share(shareData);
78
196
 
79
- case 4:
80
- _context2.next = 9;
197
+ case 12:
198
+ _context3.next = 17;
81
199
  break;
82
200
 
83
- case 6:
84
- _context2.prev = 6;
85
- _context2.t0 = _context2["catch"](0);
201
+ case 14:
202
+ _context3.prev = 14;
203
+ _context3.t0 = _context3["catch"](8);
86
204
 
87
205
  // Don't show error when user cancels the share dialog
88
- if (_context2.t0.name !== 'AbortError') {
206
+ if (_context3.t0.name !== 'AbortError') {
89
207
  showAlert({
90
208
  message: t("".concat(documentType, ".share.error.generic")),
91
209
  severity: 'error',
@@ -93,34 +211,21 @@ var ShareByLink = function ShareByLink(_ref) {
93
211
  });
94
212
  }
95
213
 
96
- case 9:
214
+ case 17:
97
215
  case "end":
98
- return _context2.stop();
216
+ return _context3.stop();
99
217
  }
100
- }, _callee2, null, [[0, 6]]);
218
+ }, _callee3, null, [[8, 14]]);
101
219
  }));
102
220
 
103
221
  return function shareLink() {
104
- return _ref3.apply(this, arguments);
222
+ return _ref4.apply(this, arguments);
105
223
  };
106
224
  }();
107
225
 
108
226
  return /*#__PURE__*/React.createElement("div", {
109
227
  className: "u-w-100 u-flex u-flex-justify-center"
110
- }, !link && /*#__PURE__*/React.createElement(Button, {
111
- label: t("".concat(documentType, ".share.shareByLink.create")),
112
- variant: "secondary",
113
- size: "medium",
114
- startIcon: /*#__PURE__*/React.createElement(Icon, {
115
- icon: LinkIcon
116
- }),
117
- className: "u-flex-auto",
118
- style: {
119
- position: 'initial'
120
- } // fix z-index bug on iOS when under a BottomDrawer due to relative position
121
- ,
122
- onClick: toggleEditDialogOpen
123
- }), showCopyAndSendButtons && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
228
+ }, showCopyAndSendButtons && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
124
229
  label: t("".concat(documentType, ".share.shareByLink.send")),
125
230
  variant: "secondary",
126
231
  size: "medium",
@@ -128,14 +233,16 @@ var ShareByLink = function ShareByLink(_ref) {
128
233
  icon: LinkIcon
129
234
  }),
130
235
  className: "u-flex-auto u-mr-half",
131
- onClick: shareLink
236
+ onClick: shareLink,
237
+ busy: isGenerating
132
238
  }), /*#__PURE__*/React.createElement(Button, {
133
239
  label: /*#__PURE__*/React.createElement(Icon, {
134
240
  icon: CopyIcon
135
241
  }),
136
242
  variant: "secondary",
137
243
  size: "medium",
138
- onClick: copyLinkToClipboard
244
+ onClick: generateLinkAndCopyLinkToClipboard,
245
+ disabled: isGenerating
139
246
  })), showOnlyCopyButton && /*#__PURE__*/React.createElement(Button, {
140
247
  label: t("".concat(documentType, ".share.shareByLink.copy")),
141
248
  variant: "secondary",
@@ -144,7 +251,8 @@ var ShareByLink = function ShareByLink(_ref) {
144
251
  icon: LinkIcon
145
252
  }),
146
253
  className: "u-flex-auto u-mr-half",
147
- onClick: copyLinkToClipboard
254
+ onClick: generateLinkAndCopyLinkToClipboard,
255
+ busy: isGenerating
148
256
  }), isEditDialogOpen && /*#__PURE__*/React.createElement(ShareRestrictionModal, {
149
257
  file: document,
150
258
  onClose: toggleEditDialogOpen
@@ -1,9 +1,82 @@
1
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import { generateWebLink } from 'cozy-client';
3
4
  import minilog from 'cozy-minilog';
5
+ import { getAppSlugFromDocumentType } from '../../helpers/link';
4
6
  var log = minilog('ShareRestrictionModal/helpers');
5
7
  export var WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH'];
6
8
  export var READ_ONLY_PERMS = ['GET'];
9
+ /**
10
+ * Create a sharing link for a file with specified options
11
+ * @param {object} options
12
+ * @param {object} options.client - CozyClient instance
13
+ * @param {object} options.file - File to share
14
+ * @param {string} options.documentType - Type of the document
15
+ * @param {Function} options.shareByLink - Function to create permissions
16
+ * @param {Function} options.t - Translation function
17
+ * @param {Function} options.showAlert - Function to display an alert
18
+ * @param {Date|string} [options.ttl] - Time to live of the sharing link
19
+ * @param {'readOnly'|'write'} [options.editingRights] - Editing rights
20
+ * @param {string} [options.password] - Password
21
+ * @param {boolean} [options.withTTL] - Whether to add a default TTL
22
+ * @returns {Promise<string|null>} - The generated web link or null if permissions creation fails
23
+ */
24
+
25
+ export var createSharingLink = /*#__PURE__*/function () {
26
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(_ref) {
27
+ var _permsResult$data;
28
+
29
+ var client, file, documentType, shareByLink, t, ttl, _ref$editingRights, editingRights, password, showAlert, permsResult, perms;
30
+
31
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
32
+ while (1) switch (_context.prev = _context.next) {
33
+ case 0:
34
+ client = _ref.client, file = _ref.file, documentType = _ref.documentType, shareByLink = _ref.shareByLink, t = _ref.t, ttl = _ref.ttl, _ref$editingRights = _ref.editingRights, editingRights = _ref$editingRights === void 0 ? 'readOnly' : _ref$editingRights, password = _ref.password, showAlert = _ref.showAlert;
35
+ _context.next = 3;
36
+ return createPermissions({
37
+ file: file,
38
+ t: t,
39
+ ttl: ttl,
40
+ editingRights: editingRights,
41
+ password: password,
42
+ documentType: documentType,
43
+ shareByLink: shareByLink,
44
+ showAlert: showAlert
45
+ });
46
+
47
+ case 3:
48
+ permsResult = _context.sent;
49
+
50
+ if (permsResult !== null && permsResult !== void 0 && (_permsResult$data = permsResult.data) !== null && _permsResult$data !== void 0 && (_permsResult$data = _permsResult$data.attributes) !== null && _permsResult$data !== void 0 && (_permsResult$data = _permsResult$data.shortcodes) !== null && _permsResult$data !== void 0 && _permsResult$data.code) {
51
+ _context.next = 6;
52
+ break;
53
+ }
54
+
55
+ return _context.abrupt("return", null);
56
+
57
+ case 6:
58
+ perms = permsResult.data;
59
+ return _context.abrupt("return", generateWebLink({
60
+ cozyUrl: client.getStackClient().uri,
61
+ searchParams: [['sharecode', perms.attributes.shortcodes.code]],
62
+ pathname: '/public',
63
+ slug: getAppSlugFromDocumentType({
64
+ documentType: documentType
65
+ }),
66
+ subDomainType: client.capabilities.flat_subdomains ? 'flat' : 'nested'
67
+ }));
68
+
69
+ case 8:
70
+ case "end":
71
+ return _context.stop();
72
+ }
73
+ }, _callee);
74
+ }));
75
+
76
+ return function createSharingLink(_x) {
77
+ return _ref2.apply(this, arguments);
78
+ };
79
+ }();
7
80
  /**
8
81
  * Copy a string to the clipboard
9
82
  *
@@ -14,27 +87,27 @@ export var READ_ONLY_PERMS = ['GET'];
14
87
  */
15
88
 
16
89
  export var copyToClipboard = /*#__PURE__*/function () {
17
- var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(value) {
18
- var _ref2,
90
+ var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(value) {
91
+ var _ref4,
19
92
  t,
20
93
  showAlert,
21
- _args = arguments;
94
+ _args2 = arguments;
22
95
 
23
- return _regeneratorRuntime.wrap(function _callee$(_context) {
24
- while (1) switch (_context.prev = _context.next) {
96
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
97
+ while (1) switch (_context2.prev = _context2.next) {
25
98
  case 0:
26
- _ref2 = _args.length > 1 && _args[1] !== undefined ? _args[1] : {}, t = _ref2.t, showAlert = _ref2.showAlert;
99
+ _ref4 = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {}, t = _ref4.t, showAlert = _ref4.showAlert;
27
100
 
28
101
  if (value) {
29
- _context.next = 3;
102
+ _context2.next = 3;
30
103
  break;
31
104
  }
32
105
 
33
- return _context.abrupt("return", false);
106
+ return _context2.abrupt("return", false);
34
107
 
35
108
  case 3:
36
- _context.prev = 3;
37
- _context.next = 6;
109
+ _context2.prev = 3;
110
+ _context2.next = 6;
38
111
  return navigator.clipboard.writeText(value);
39
112
 
40
113
  case 6:
@@ -43,28 +116,28 @@ export var copyToClipboard = /*#__PURE__*/function () {
43
116
  severity: 'success',
44
117
  variant: 'filled'
45
118
  });
46
- _context.next = 13;
119
+ _context2.next = 13;
47
120
  break;
48
121
 
49
122
  case 9:
50
- _context.prev = 9;
51
- _context.t0 = _context["catch"](3);
123
+ _context2.prev = 9;
124
+ _context2.t0 = _context2["catch"](3);
52
125
  showAlert({
53
126
  message: t('copyReminderContent.error'),
54
127
  severity: 'error',
55
128
  variant: 'filled'
56
129
  });
57
- log.error("Error in 'copyToClipboard' function when trying to copy to clipboard", _context.t0);
130
+ log.error("Error in 'copyToClipboard' function when trying to copy to clipboard", _context2.t0);
58
131
 
59
132
  case 13:
60
133
  case "end":
61
- return _context.stop();
134
+ return _context2.stop();
62
135
  }
63
- }, _callee, null, [[3, 9]]);
136
+ }, _callee2, null, [[3, 9]]);
64
137
  }));
65
138
 
66
- return function copyToClipboard(_x) {
67
- return _ref.apply(this, arguments);
139
+ return function copyToClipboard(_x2) {
140
+ return _ref3.apply(this, arguments);
68
141
  };
69
142
  }();
70
143
  /**
@@ -115,39 +188,39 @@ export var makeTTL = function makeTTL(selectedDate) {
115
188
  */
116
189
 
117
190
  export var createPermissions = /*#__PURE__*/function () {
118
- var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(_ref3) {
191
+ var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(_ref5) {
119
192
  var file, t, ttl, password, editingRights, documentType, shareByLink, showAlert, verbs;
120
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
121
- while (1) switch (_context2.prev = _context2.next) {
193
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
194
+ while (1) switch (_context3.prev = _context3.next) {
122
195
  case 0:
123
- file = _ref3.file, t = _ref3.t, ttl = _ref3.ttl, password = _ref3.password, editingRights = _ref3.editingRights, documentType = _ref3.documentType, shareByLink = _ref3.shareByLink, showAlert = _ref3.showAlert;
124
- _context2.prev = 1;
196
+ file = _ref5.file, t = _ref5.t, ttl = _ref5.ttl, password = _ref5.password, editingRights = _ref5.editingRights, documentType = _ref5.documentType, shareByLink = _ref5.shareByLink, showAlert = _ref5.showAlert;
197
+ _context3.prev = 1;
125
198
  verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS;
126
- return _context2.abrupt("return", shareByLink(file, {
199
+ return _context3.abrupt("return", shareByLink(file, {
127
200
  verbs: verbs,
128
201
  ttl: ttl,
129
202
  password: password
130
203
  }));
131
204
 
132
205
  case 6:
133
- _context2.prev = 6;
134
- _context2.t0 = _context2["catch"](1);
206
+ _context3.prev = 6;
207
+ _context3.t0 = _context3["catch"](1);
135
208
  showAlert({
136
209
  message: t("".concat(documentType, ".share.shareByLink.permserror")),
137
210
  severity: 'error',
138
211
  variant: 'filled'
139
212
  });
140
- log.error("Error in 'readOnlyPermissionLink' function when trying to change permission", _context2.t0);
213
+ log.error("Error in 'readOnlyPermissionLink' function when trying to change permission", _context3.t0);
141
214
 
142
215
  case 10:
143
216
  case "end":
144
- return _context2.stop();
217
+ return _context3.stop();
145
218
  }
146
- }, _callee2, null, [[1, 6]]);
219
+ }, _callee3, null, [[1, 6]]);
147
220
  }));
148
221
 
149
- return function createPermissions(_x2) {
150
- return _ref4.apply(this, arguments);
222
+ return function createPermissions(_x3) {
223
+ return _ref6.apply(this, arguments);
151
224
  };
152
225
  }();
153
226
  /**
@@ -166,41 +239,41 @@ export var createPermissions = /*#__PURE__*/function () {
166
239
  */
167
240
 
168
241
  export var updatePermissions = /*#__PURE__*/function () {
169
- var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(_ref5) {
242
+ var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref7) {
170
243
  var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, expiresAt, ensurePassword, verbs;
171
- return _regeneratorRuntime.wrap(function _callee3$(_context3) {
172
- while (1) switch (_context3.prev = _context3.next) {
244
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
245
+ while (1) switch (_context4.prev = _context4.next) {
173
246
  case 0:
174
- file = _ref5.file, t = _ref5.t, dateToggle = _ref5.dateToggle, selectedDate = _ref5.selectedDate, passwordToggle = _ref5.passwordToggle, password = _ref5.password, editingRights = _ref5.editingRights, documentType = _ref5.documentType, updateDocumentPermissions = _ref5.updateDocumentPermissions, showAlert = _ref5.showAlert;
175
- _context3.prev = 1;
247
+ file = _ref7.file, t = _ref7.t, dateToggle = _ref7.dateToggle, selectedDate = _ref7.selectedDate, passwordToggle = _ref7.passwordToggle, password = _ref7.password, editingRights = _ref7.editingRights, documentType = _ref7.documentType, updateDocumentPermissions = _ref7.updateDocumentPermissions, showAlert = _ref7.showAlert;
248
+ _context4.prev = 1;
176
249
  expiresAt = dateToggle ? (selectedDate === null || selectedDate === void 0 ? void 0 : selectedDate.toISOString()) || undefined : '';
177
250
  ensurePassword = passwordToggle ? password || undefined : '';
178
251
  verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS;
179
- return _context3.abrupt("return", updateDocumentPermissions(file, {
252
+ return _context4.abrupt("return", updateDocumentPermissions(file, {
180
253
  verbs: verbs,
181
254
  expiresAt: expiresAt,
182
255
  password: ensurePassword
183
256
  }));
184
257
 
185
258
  case 8:
186
- _context3.prev = 8;
187
- _context3.t0 = _context3["catch"](1);
259
+ _context4.prev = 8;
260
+ _context4.t0 = _context4["catch"](1);
188
261
  showAlert({
189
262
  message: t("".concat(documentType, ".share.shareByLink.permserror")),
190
263
  severity: 'error',
191
264
  variant: 'filled'
192
265
  });
193
- log.error("Error in 'updateDocumentPermissions' function when trying to change permission", _context3.t0);
266
+ log.error("Error in 'updateDocumentPermissions' function when trying to change permission", _context4.t0);
194
267
 
195
268
  case 12:
196
269
  case "end":
197
- return _context3.stop();
270
+ return _context4.stop();
198
271
  }
199
- }, _callee3, null, [[1, 8]]);
272
+ }, _callee4, null, [[1, 8]]);
200
273
  }));
201
274
 
202
- return function updatePermissions(_x3) {
203
- return _ref6.apply(this, arguments);
275
+ return function updatePermissions(_x4) {
276
+ return _ref8.apply(this, arguments);
204
277
  };
205
278
  }();
206
279
  /**
@@ -214,33 +287,33 @@ export var updatePermissions = /*#__PURE__*/function () {
214
287
  */
215
288
 
216
289
  export var revokePermissions = /*#__PURE__*/function () {
217
- var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref7) {
290
+ var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(_ref9) {
218
291
  var file, t, documentType, revokeSharingLink, showAlert;
219
- return _regeneratorRuntime.wrap(function _callee4$(_context4) {
220
- while (1) switch (_context4.prev = _context4.next) {
292
+ return _regeneratorRuntime.wrap(function _callee5$(_context5) {
293
+ while (1) switch (_context5.prev = _context5.next) {
221
294
  case 0:
222
- file = _ref7.file, t = _ref7.t, documentType = _ref7.documentType, revokeSharingLink = _ref7.revokeSharingLink, showAlert = _ref7.showAlert;
223
- _context4.prev = 1;
224
- return _context4.abrupt("return", revokeSharingLink(file));
295
+ file = _ref9.file, t = _ref9.t, documentType = _ref9.documentType, revokeSharingLink = _ref9.revokeSharingLink, showAlert = _ref9.showAlert;
296
+ _context5.prev = 1;
297
+ return _context5.abrupt("return", revokeSharingLink(file));
225
298
 
226
299
  case 5:
227
- _context4.prev = 5;
228
- _context4.t0 = _context4["catch"](1);
300
+ _context5.prev = 5;
301
+ _context5.t0 = _context5["catch"](1);
229
302
  showAlert({
230
303
  message: t("".concat(documentType, ".share.error.revoke")),
231
304
  severity: 'error',
232
305
  variant: 'filled'
233
306
  });
234
- log.error("Error in 'revokePermissions' function when trying to revoke sharing link", _context4.t0);
307
+ log.error("Error in 'revokePermissions' function when trying to revoke sharing link", _context5.t0);
235
308
 
236
309
  case 9:
237
310
  case "end":
238
- return _context4.stop();
311
+ return _context5.stop();
239
312
  }
240
- }, _callee4, null, [[1, 5]]);
313
+ }, _callee5, null, [[1, 5]]);
241
314
  }));
242
315
 
243
- return function revokePermissions(_x4) {
244
- return _ref8.apply(this, arguments);
316
+ return function revokePermissions(_x5) {
317
+ return _ref10.apply(this, arguments);
245
318
  };
246
319
  }();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "28.2.0",
3
+ "version": "28.3.0",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -59,7 +59,7 @@
59
59
  "cozy-intent": "^2.30.1",
60
60
  "cozy-minilog": "^3.10.0",
61
61
  "cozy-ui": "^135.0.0",
62
- "cozy-ui-plus": "^4.3.0",
62
+ "cozy-ui-plus": "^4.4.0",
63
63
  "jest": "26.6.3",
64
64
  "jest-environment-jsdom": "26.6.2",
65
65
  "react": "16.12.0",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "ab4b92d67cde105ce9d2c17693c6fe2245e77627"
86
+ "gitHead": "5257ba90033080856ea9c08920e8007fcc8a027c"
87
87
  }
@@ -1,7 +1,8 @@
1
1
  import PropTypes from 'prop-types'
2
- import React, { useReducer } from 'react'
2
+ import React, { useReducer, useState } from 'react'
3
3
  import { useI18n } from 'twake-i18n'
4
4
 
5
+ import { useClient } from 'cozy-client'
5
6
  import Button from 'cozy-ui/transpiled/react/Buttons'
6
7
  import Icon from 'cozy-ui/transpiled/react/Icon'
7
8
  import CopyIcon from 'cozy-ui/transpiled/react/Icons/Copy'
@@ -10,33 +11,86 @@ import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
10
11
  import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints'
11
12
 
12
13
  import { ShareRestrictionModal } from './ShareRestrictionModal/ShareRestrictionModal'
13
- import { copyToClipboard } from './ShareRestrictionModal/helpers'
14
+ import {
15
+ copyToClipboard,
16
+ createSharingLink
17
+ } from './ShareRestrictionModal/helpers'
18
+ import { useSharingContext } from '../hooks/useSharingContext'
14
19
 
15
20
  const ShareByLink = ({ link, document, documentType }) => {
16
21
  const { t } = useI18n()
17
22
  const { isMobile } = useBreakpoints()
18
23
  const { showAlert } = useAlert()
24
+ const client = useClient()
25
+ const { shareByLink } = useSharingContext()
26
+ const [isGenerating, setIsGenerating] = useState(false)
19
27
 
20
- const showCopyAndSendButtons = link && isMobile && navigator.share
21
- const showOnlyCopyButton =
22
- (link && !isMobile) || (link && isMobile && !navigator.share)
28
+ const canShare = typeof navigator?.share === 'function'
29
+ const showCopyAndSendButtons = isMobile && canShare
30
+ const showOnlyCopyButton = !isMobile || !canShare
23
31
 
24
32
  const [isEditDialogOpen, toggleEditDialogOpen] = useReducer(
25
33
  state => !state,
26
34
  false
27
35
  )
28
36
 
29
- const copyLinkToClipboard = async () => {
30
- await copyToClipboard(link, { t, showAlert })
37
+ const handleGenerateLink = async () => {
38
+ setIsGenerating(true)
39
+ try {
40
+ return await createSharingLink({
41
+ client,
42
+ file: document,
43
+ documentType,
44
+ shareByLink,
45
+ t,
46
+ showAlert,
47
+ password: '',
48
+ ttl: undefined,
49
+ editingRights: 'readOnly'
50
+ })
51
+ } catch (error) {
52
+ return null
53
+ } finally {
54
+ setIsGenerating(false)
55
+ }
56
+ }
57
+
58
+ const generateLinkAndCopyLinkToClipboard = async () => {
59
+ let linkToCopy = link
60
+ if (!linkToCopy) {
61
+ linkToCopy = await handleGenerateLink()
62
+ }
63
+ if (linkToCopy) {
64
+ await copyToClipboard(linkToCopy, { t, showAlert })
65
+ } else {
66
+ showAlert({
67
+ message: t(`${documentType}.share.error.generic`),
68
+ severity: 'error',
69
+ variant: 'filled'
70
+ })
71
+ }
31
72
  }
32
73
 
33
74
  const shareLink = async () => {
75
+ let linkToShare = link
76
+ if (!linkToShare) {
77
+ linkToShare = await handleGenerateLink()
78
+ }
79
+ if (!linkToShare) {
80
+ showAlert({
81
+ message: t(`${documentType}.share.error.generic`),
82
+ severity: 'error',
83
+ variant: 'filled'
84
+ })
85
+ return
86
+ }
87
+
34
88
  try {
35
89
  const shareData = {
36
90
  text: t(`${documentType}.share.shareByLink.shareDescription`, {
37
91
  name: document.name || ''
38
92
  }),
39
- url: link
93
+ url: linkToShare
40
94
  }
41
95
  await navigator.share(shareData)
42
96
  } catch (error) {
@@ -53,17 +107,6 @@ const ShareByLink = ({ link, document, documentType }) => {
53
107
 
54
108
  return (
55
109
  <div className="u-w-100 u-flex u-flex-justify-center">
56
- {!link && (
57
- <Button
58
- label={t(`${documentType}.share.shareByLink.create`)}
59
- variant="secondary"
60
- size="medium"
61
- startIcon={<Icon icon={LinkIcon} />}
62
- className="u-flex-auto"
63
- style={{ position: 'initial' }} // fix z-index bug on iOS when under a BottomDrawer due to relative position
64
- onClick={toggleEditDialogOpen}
65
- />
66
- )}
67
110
  {showCopyAndSendButtons && (
68
111
  <>
69
112
  <Button
@@ -73,12 +116,14 @@ const ShareByLink = ({ link, document, documentType }) => {
73
116
  startIcon={<Icon icon={LinkIcon} />}
74
117
  className="u-flex-auto u-mr-half"
75
118
  onClick={shareLink}
119
+ busy={isGenerating}
76
120
  />
77
121
  <Button
78
122
  label={<Icon icon={CopyIcon} />}
79
123
  variant="secondary"
80
124
  size="medium"
81
- onClick={copyLinkToClipboard}
125
+ onClick={generateLinkAndCopyLinkToClipboard}
126
+ disabled={isGenerating}
82
127
  />
83
128
  </>
84
129
  )}
@@ -89,7 +134,8 @@ const ShareByLink = ({ link, document, documentType }) => {
89
134
  size="medium"
90
135
  startIcon={<Icon icon={LinkIcon} />}
91
136
  className="u-flex-auto u-mr-half"
92
- onClick={copyLinkToClipboard}
137
+ onClick={generateLinkAndCopyLinkToClipboard}
138
+ busy={isGenerating}
93
139
  />
94
140
  )}
95
141
  {isEditDialogOpen && (
@@ -1,10 +1,62 @@
1
+ import { generateWebLink } from 'cozy-client'
1
2
  import minilog from 'cozy-minilog'
2
3
 
4
+ import { getAppSlugFromDocumentType } from '../../helpers/link'
5
+
3
6
  const log = minilog('ShareRestrictionModal/helpers')
4
7
 
5
8
  export const WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH']
6
9
  export const READ_ONLY_PERMS = ['GET']
7
10
 
11
+ /**
12
+ * Create a sharing link for a file with specified options
13
+ * @param {object} options
14
+ * @param {object} options.client - CozyClient instance
15
+ * @param {object} options.file - File to share
16
+ * @param {string} options.documentType - Type of the document
17
+ * @param {Function} options.shareByLink - Function to create permissions
18
+ * @param {Function} options.t - Translation function
19
+ * @param {Function} options.showAlert - Function to display an alert
20
+ * @param {Date|string} [options.ttl] - Time to live of the sharing link
21
+ * @param {'readOnly'|'write'} [options.editingRights] - Editing rights
22
+ * @param {string} [options.password] - Password
23
+ * @param {boolean} [options.withTTL] - Whether to add a default TTL
24
+ * @returns {Promise<string|null>} - The generated web link or null if permissions creation fails
25
+ */
26
+ export const createSharingLink = async ({
27
+ client,
28
+ file,
29
+ documentType,
30
+ shareByLink,
31
+ t,
32
+ ttl,
33
+ editingRights = 'readOnly',
34
+ password,
35
+ showAlert
36
+ }) => {
37
+ const permsResult = await createPermissions({
38
+ file,
39
+ t,
40
+ ttl,
41
+ editingRights,
42
+ password,
43
+ documentType,
44
+ shareByLink,
45
+ showAlert
46
+ })
47
+ if (!permsResult?.data?.attributes?.shortcodes?.code) {
48
+ return null
49
+ }
50
+ const { data: perms } = permsResult
51
+ return generateWebLink({
52
+ cozyUrl: client.getStackClient().uri,
53
+ searchParams: [['sharecode', perms.attributes.shortcodes.code]],
54
+ pathname: '/public',
55
+ slug: getAppSlugFromDocumentType({ documentType }),
56
+ subDomainType: client.capabilities.flat_subdomains ? 'flat' : 'nested'
57
+ })
58
+ }
59
+
8
60
  /**
9
61
  * Copy a string to the clipboard
10
62
  *