diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5d1d766720..34607b0730 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -180,7 +180,7 @@ "dependencies": { "reactstrap": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/reactstrap/-/reactstrap-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-5.0.0.tgz", "integrity": "sha512-y0eju/LAK7gbEaTFfq2iW92MF7/5Qh0tc1LgYr2mg92IX8NodGc03a+I+cp7bJ0VXHAiLy0bFL9UP89oSm4cBg==", "requires": { "classnames": "^2.2.3", @@ -640,7 +640,7 @@ }, "axios": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "requires": { "follow-redirects": "^1.3.0", @@ -2717,6 +2717,14 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-to-clipboard": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", + "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", + "requires": { + "toggle-selection": "^1.0.3" + } + }, "core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", @@ -9964,7 +9972,7 @@ }, "react-popper": { "version": "0.8.3", - "resolved": "http://registry.npmjs.org/react-popper/-/react-popper-0.8.3.tgz", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.8.3.tgz", "integrity": "sha1-D3MzMTfJ+wr27EB00tBYWgoEYeE=", "requires": { "popper.js": "^1.12.9", diff --git a/frontend/package.json b/frontend/package.json index cd4ddbda5d..02871190a6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "MD5": "^1.3.0", "autoprefixer": "7.1.6", "classnames": "^2.2.6", + "copy-to-clipboard": "^3.0.8", "css-loader": "0.28.7", "dotenv": "4.0.0", "dotenv-expand": "4.2.0", diff --git a/frontend/src/components/dialog/generate-share-link.js b/frontend/src/components/dialog/generate-share-link.js index e96e796a32..32a0bb0b9f 100644 --- a/frontend/src/components/dialog/generate-share-link.js +++ b/frontend/src/components/dialog/generate-share-link.js @@ -1,8 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import moment from 'moment'; +import copy from 'copy-to-clipboard'; +import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon } from 'reactstrap'; import { gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; -import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon } from 'reactstrap'; +import SharedLinkInfo from '../../models/shared-link-info'; +import toaster from '../toast'; const propTypes = { itemPath: PropTypes.string.isRequired, @@ -21,9 +25,9 @@ class GenerateShareLink extends React.Component { password: '', passwdnew: '', expireDays: '', - token: '', - link: '', - errorInfo: '' + errorInfo: '', + sharedLinkInfo: null, + isNoticeMessageShow: false, }; this.permissions = { 'can_edit': false, @@ -41,10 +45,8 @@ class GenerateShareLink extends React.Component { let repoID = this.props.repoID; seafileAPI.getShareLink(repoID, path).then((res) => { if (res.data.length !== 0) { - this.setState({ - link: res.data[0].link, - token: res.data[0].token, - }); + let sharedLinkInfo = new SharedLinkInfo(res.data[0]); + this.setState({sharedLinkInfo: sharedLinkInfo}); } }); } @@ -105,25 +107,36 @@ class GenerateShareLink extends React.Component { let permissions = this.permissions; permissions = JSON.stringify(permissions); seafileAPI.createShareLink(repoID, itemPath, password, expireDays, permissions).then((res) => { - this.setState({ - link: res.data.link, - token: res.data.token - }); + let sharedLinkInfo = new SharedLinkInfo(res.data); + this.setState({sharedLinkInfo: sharedLinkInfo}); }); } } + onCopySharedLink = () => { + let sharedLink = this.state.sharedLinkInfo.link; + copy(sharedLink); + toaster.success(gettext('Share link is copied to the clipboard.')); + } + + onCopyDownloadLink = () => { + let downloadLink = this.state.sharedLinkInfo.link + '?dl'; + copy(downloadLink); + toaster.success(gettext('Direct download link is copied to the clipboard.')); + } + deleteShareLink = () => { - seafileAPI.deleteShareLink(this.state.token).then(() => { + let sharedLinkInfo = this.state.sharedLinkInfo; + seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => { this.setState({ - link: '', - token: '', password: '', passwordnew: '', isShowPasswordInput: false, expireDays: '', isExpireChecked: false, errorInfo: '', + sharedLinkInfo: null, + isNoticeMessageShow: false, }); this.permissions = { 'can_edit': false, @@ -216,13 +229,55 @@ class GenerateShareLink extends React.Component { return true; } + onNoticeMessageToggle = () => { + this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow}); + } + render() { - if (this.state.link) { + if (this.state.sharedLinkInfo) { + let sharedLinkInfo = this.state.sharedLinkInfo; return ( -
-

{this.state.link}

- -
+
+
+ +
{gettext('Link:')}
+
+ {sharedLinkInfo.link}{' '} + {sharedLinkInfo.is_expired ? + ({gettext('Expired')}) : + + } +
+
+ {!sharedLinkInfo.is_dir && ( //just for file + +
{gettext('Direct Download Link:')}
+
+ {sharedLinkInfo.link}?dl{' '} + {sharedLinkInfo.is_expired ? + ({gettext('Expired')}) : + + } +
+
+ )} + {sharedLinkInfo.expire_date && ( + +
{gettext('Expiration Date:')}
+
{moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD hh:mm:ss')}
+
+ )} +
+ {!this.state.isNoticeMessageShow ? + : +
+

{gettext('Are you sure you want to delete the share link?')}

+

{gettext('If the share link is deleted, no one will be able to access it any more.')}

+ {' '} + +
+ } +
); } else { return ( diff --git a/frontend/src/components/dialog/generate-upload-link.js b/frontend/src/components/dialog/generate-upload-link.js index 250f0d23de..52e12ac0f9 100644 --- a/frontend/src/components/dialog/generate-upload-link.js +++ b/frontend/src/components/dialog/generate-upload-link.js @@ -1,8 +1,11 @@ import React from 'react'; -import { gettext } from '../../utils/constants'; import PropTypes from 'prop-types'; -import { seafileAPI } from '../../utils/seafile-api'; +import copy from 'copy-to-clipboard'; import { Button, Form, FormGroup, FormText, Label, Input, InputGroup, InputGroupAddon } from 'reactstrap'; +import { gettext } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import SharedUploadInfo from '../../models/shared-upload-info'; +import toaster from '../toast'; const propTypes = { itemPath: PropTypes.string.isRequired, @@ -17,8 +20,7 @@ class GenerateUploadLink extends React.Component { passwordVisible: false, password: '', passwdnew: '', - link: '', - token:'' + sharedUploadInfo: null, }; } @@ -31,10 +33,8 @@ class GenerateUploadLink extends React.Component { let repoID = this.props.repoID; seafileAPI.getUploadLinks(repoID, path).then((res) => { if (res.data.length !== 0) { - this.setState({ - link: res.data[0].link, - token: res.data[0].token, - }); + let sharedUploadInfo = new SharedUploadInfo(res.data[0]); + this.setState({sharedUploadInfo: sharedUploadInfo}); } }); } @@ -94,33 +94,46 @@ class GenerateUploadLink extends React.Component { }); } else { seafileAPI.createUploadLink(repoID, path, this.state.password).then((res) => { - this.setState({ - link: res.data.link, - token: res.data.token - }); + let sharedUploadInfo = new SharedUploadInfo(res.data); + this.setState({sharedUploadInfo: sharedUploadInfo}); }); } } + onCopyUploadLink = () => { + let uploadLink = this.state.sharedUploadInfo.link; + copy(uploadLink); + toaster.success(gettext('Upload link is copied to the clipboard.')); + } + deleteUploadLink = () => { - seafileAPI.deleteUploadLink(this.state.token).then(() => { + let sharedUploadInfo = this.state.sharedUploadInfo + seafileAPI.deleteUploadLink(sharedUploadInfo.token).then(() => { this.setState({ - link: '', - token: '', showPasswordInput: false, password: '', passwordnew: '', + sharedUploadInfo: null, }); }); } render() { - if (this.state.link) { + if (this.state.sharedUploadInfo) { + let sharedUploadInfo = this.state.sharedUploadInfo; return ( -
-

{this.state.link}

+
+ + +
{gettext('Upload Link:')}
+
+ {sharedUploadInfo.link} + +
+
+ - +
); } return ( diff --git a/frontend/src/components/toast/toastManager.js b/frontend/src/components/toast/toastManager.js index 65d1338328..370f899bfa 100644 --- a/frontend/src/components/toast/toastManager.js +++ b/frontend/src/components/toast/toastManager.js @@ -11,7 +11,7 @@ const wrapperClass = css({ left: 0, right: 0, position: 'fixed', - zIndex: 30 + zIndex: 999999, }); diff --git a/frontend/src/css/share-link-dialog.css b/frontend/src/css/share-link-dialog.css index 21b35a2a7e..2afdd45a11 100644 --- a/frontend/src/css/share-link-dialog.css +++ b/frontend/src/css/share-link-dialog.css @@ -67,8 +67,9 @@ input.expire-input { display: inline-block; - width: 5rem; + width: 4rem; height: 1.5rem; + padding: 0.25rem 0.25rem; margin: 0 0.25rem 0 1.25rem; }