2022-03-10 06:14:15 +00:00
import React from 'react' ;
2018-12-06 03:28:16 +00:00
import PropTypes from 'prop-types' ;
2019-01-04 10:11:13 +00:00
import copy from 'copy-to-clipboard' ;
2019-07-02 03:06:39 +00:00
import moment from 'moment' ;
2022-03-10 06:14:15 +00:00
import { Button , Form , FormGroup , Label , Input , InputGroup , InputGroupAddon , Alert } from 'reactstrap' ;
2021-08-12 07:02:03 +00:00
import { gettext , shareLinkForceUsePassword , shareLinkPasswordMinLength , shareLinkPasswordStrengthLevel , canSendShareLinkEmail , uploadLinkExpireDaysMin , uploadLinkExpireDaysMax , uploadLinkExpireDaysDefault } from '../../utils/constants' ;
2019-01-04 10:11:13 +00:00
import { seafileAPI } from '../../utils/seafile-api' ;
2019-05-06 08:08:34 +00:00
import { Utils } from '../../utils/utils' ;
2019-08-21 08:36:55 +00:00
import UploadLink from '../../models/upload-link' ;
2019-01-04 10:11:13 +00:00
import toaster from '../toast' ;
2019-06-27 06:08:10 +00:00
import SendLink from '../send-link' ;
2021-01-22 10:18:13 +00:00
import SharedLink from '../shared-link' ;
2022-03-10 06:14:15 +00:00
import SetLinkExpiration from '../set-link-expiration' ;
2018-12-06 03:28:16 +00:00
const propTypes = {
itemPath : PropTypes . string . isRequired ,
2019-01-05 12:40:41 +00:00
repoID : PropTypes . string . isRequired ,
closeShareDialog : PropTypes . func . isRequired ,
2018-12-06 03:28:16 +00:00
} ;
2020-05-21 03:32:02 +00:00
const inputWidth = Utils . isDesktop ( ) ? 250 : 210 ;
2018-12-06 03:28:16 +00:00
class GenerateUploadLink extends React . Component {
constructor ( props ) {
super ( props ) ;
2020-07-21 10:22:45 +00:00
this . isExpireDaysNoLimit = ( uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax === 0 && uploadLinkExpireDaysDefault == 0 ) ;
this . defaultExpireDays = this . isExpireDaysNoLimit ? '' : uploadLinkExpireDaysDefault ;
2018-12-06 03:28:16 +00:00
this . state = {
2021-08-12 07:02:03 +00:00
showPasswordInput : shareLinkForceUsePassword ? true : false ,
2018-12-06 03:28:16 +00:00
passwordVisible : false ,
password : '' ,
2021-08-12 07:02:03 +00:00
passwordnew : '' ,
2021-11-23 04:09:08 +00:00
storedPasswordVisible : false ,
2019-01-04 10:11:13 +00:00
sharedUploadInfo : null ,
2019-07-02 03:06:39 +00:00
isSendLinkShown : false ,
2020-07-21 10:22:45 +00:00
isExpireChecked : ! this . isExpireDaysNoLimit ,
2022-02-24 02:47:17 +00:00
isExpirationEditIconShow : false ,
isEditingExpiration : false ,
2022-03-10 06:14:15 +00:00
expType : 'by-days' ,
2020-07-21 10:22:45 +00:00
expireDays : this . defaultExpireDays ,
2020-05-21 03:32:02 +00:00
expDate : null
2018-12-06 03:28:16 +00:00
} ;
}
componentDidMount ( ) {
this . getUploadLink ( ) ;
}
getUploadLink = ( ) => {
let path = this . props . itemPath ;
2020-11-02 05:56:35 +00:00
let repoID = this . props . repoID ;
2020-01-11 05:29:04 +00:00
seafileAPI . getUploadLink ( repoID , path ) . then ( ( res ) => {
2018-12-06 03:28:16 +00:00
if ( res . data . length !== 0 ) {
2019-08-21 08:36:55 +00:00
let sharedUploadInfo = new UploadLink ( res . data [ 0 ] ) ;
2019-01-04 10:11:13 +00:00
this . setState ( { sharedUploadInfo : sharedUploadInfo } ) ;
2018-12-06 03:28:16 +00:00
}
2019-05-12 07:05:53 +00:00
} ) . catch ( ( err ) => {
2019-12-19 05:44:30 +00:00
let errMsg = Utils . getErrorMsg ( err , true ) ;
if ( ! err . response || err . response . status !== 403 ) {
toaster . danger ( errMsg ) ;
2019-05-12 07:05:53 +00:00
}
2019-12-19 05:44:30 +00:00
this . props . closeShareDialog ( ) ;
2018-12-06 03:28:16 +00:00
} ) ;
}
addPassword = ( ) => {
this . setState ( {
showPasswordInput : ! this . state . showPasswordInput ,
password : '' ,
2021-08-12 07:02:03 +00:00
passwordnew : '' ,
2018-12-06 03:28:16 +00:00
errorInfo : ''
} ) ;
}
togglePasswordVisible = ( ) => {
this . setState ( {
passwordVisible : ! this . state . passwordVisible
} ) ;
}
generatePassword = ( ) => {
2019-05-06 08:08:34 +00:00
let val = Utils . generatePassword ( shareLinkPasswordMinLength ) ;
2018-12-06 03:28:16 +00:00
this . setState ( {
password : val ,
passwordnew : val
} ) ;
}
inputPassword = ( e ) => {
this . setState ( {
password : e . target . value
} ) ;
}
inputPasswordNew = ( e ) => {
this . setState ( {
passwordnew : e . target . value
} ) ;
}
2021-11-23 04:09:08 +00:00
toggleStoredPasswordVisible = ( ) => {
this . setState ( {
storedPasswordVisible : ! this . state . storedPasswordVisible
} ) ;
}
2018-12-06 03:28:16 +00:00
generateUploadLink = ( ) => {
2019-07-02 03:06:39 +00:00
let isValid = this . validateParamsInput ( ) ;
if ( isValid ) {
2020-05-21 03:32:02 +00:00
this . setState ( { errorInfo : '' } ) ;
let { itemPath , repoID } = this . props ;
2022-03-10 06:14:15 +00:00
let { password , isExpireChecked , expType , expireDays , expDate } = this . state ;
2020-05-21 03:32:02 +00:00
let expirationTime = '' ;
if ( isExpireChecked ) {
2022-03-10 06:14:15 +00:00
if ( expType == 'by-days' ) {
2020-05-21 03:32:02 +00:00
expirationTime = moment ( ) . add ( parseInt ( expireDays ) , 'days' ) . format ( ) ;
} else {
expirationTime = expDate . format ( ) ;
}
}
seafileAPI . createUploadLink ( repoID , itemPath , password , expirationTime ) . then ( ( res ) => {
2019-08-21 08:36:55 +00:00
let sharedUploadInfo = new UploadLink ( res . data ) ;
2020-11-02 05:56:35 +00:00
this . setState ( { sharedUploadInfo : sharedUploadInfo } ) ;
2019-07-16 02:01:09 +00:00
} ) . catch ( error => {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
2018-12-06 03:28:16 +00:00
} ) ;
}
}
2019-07-02 03:06:39 +00:00
validateParamsInput = ( ) => {
2022-03-10 06:14:15 +00:00
let { showPasswordInput , password , passwordnew , isExpireChecked , expType , expireDays , expDate } = this . state ;
2019-07-02 03:06:39 +00:00
// check password params
if ( showPasswordInput ) {
if ( password . length === 0 ) {
2022-02-17 07:30:06 +00:00
this . setState ( { errorInfo : gettext ( 'Please enter a password.' ) } ) ;
2019-07-02 03:06:39 +00:00
return false ;
}
if ( password . length < shareLinkPasswordMinLength ) {
2022-02-17 07:30:06 +00:00
this . setState ( { errorInfo : gettext ( 'The password is too short.' ) } ) ;
2019-07-02 03:06:39 +00:00
return false ;
}
if ( password !== passwordnew ) {
2019-07-04 10:14:13 +00:00
this . setState ( { errorInfo : gettext ( 'Passwords don\'t match' ) } ) ;
2019-07-02 03:06:39 +00:00
return false ;
}
2021-08-12 07:02:03 +00:00
if ( Utils . getStrengthLevel ( password ) < shareLinkPasswordStrengthLevel ) {
2022-02-17 07:30:06 +00:00
this . setState ( { errorInfo : gettext ( 'The password is too weak. It should include at least {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols.' ) . replace ( '{passwordStrengthLevel}' , shareLinkPasswordStrengthLevel ) } ) ;
2021-08-12 07:02:03 +00:00
return false ;
}
2019-07-02 03:06:39 +00:00
}
if ( isExpireChecked ) {
2022-03-10 06:14:15 +00:00
if ( expType == 'by-date' ) {
2020-05-21 03:32:02 +00:00
if ( ! expDate ) {
2022-02-17 07:46:03 +00:00
this . setState ( { errorInfo : gettext ( 'Please select an expiration time' ) } ) ;
2020-05-21 03:32:02 +00:00
return false ;
}
return true ;
}
let reg = /^\d+$/ ;
2019-07-02 03:06:39 +00:00
if ( ! expireDays ) {
2019-07-04 10:14:13 +00:00
this . setState ( { errorInfo : gettext ( 'Please enter days' ) } ) ;
2019-07-02 03:06:39 +00:00
return false ;
}
if ( ! reg . test ( expireDays ) ) {
2019-07-04 10:14:13 +00:00
this . setState ( { errorInfo : gettext ( 'Please enter a non-negative integer' ) } ) ;
2019-07-02 03:06:39 +00:00
return false ;
}
this . setState ( { expireDays : parseInt ( expireDays ) } ) ;
}
return true ;
}
onExpireChecked = ( e ) => {
this . setState ( { isExpireChecked : e . target . checked } ) ;
}
2022-03-10 06:14:15 +00:00
setExpType = ( e ) => {
2022-02-24 02:47:17 +00:00
this . setState ( {
2022-03-10 06:14:15 +00:00
expType : e . target . value
2022-02-24 02:47:17 +00:00
} ) ;
}
2020-05-21 03:32:02 +00:00
onExpDateChanged = ( value ) => {
this . setState ( {
expDate : value
} ) ;
}
2019-07-02 03:06:39 +00:00
onExpireDaysChanged = ( e ) => {
let day = e . target . value . trim ( ) ;
this . setState ( { expireDays : day } ) ;
}
2019-01-04 10:11:13 +00:00
onCopyUploadLink = ( ) => {
let uploadLink = this . state . sharedUploadInfo . link ;
copy ( uploadLink ) ;
toaster . success ( gettext ( 'Upload link is copied to the clipboard.' ) ) ;
2019-01-05 12:40:41 +00:00
this . props . closeShareDialog ( ) ;
2019-01-04 10:11:13 +00:00
}
2022-02-24 02:47:17 +00:00
handleMouseOverExpirationEditIcon = ( ) => {
this . setState ( { isExpirationEditIconShow : true } ) ;
}
handleMouseOutExpirationEditIcon = ( ) => {
this . setState ( { isExpirationEditIconShow : false } ) ;
}
editExpirationToggle = ( ) => {
this . setState ( { isEditingExpiration : ! this . state . isEditingExpiration } ) ;
}
updateExpiration = ( e ) => {
e . preventDefault ( ) ;
e . nativeEvent . stopImmediatePropagation ( ) ;
2022-03-10 06:14:15 +00:00
let { expType , expireDays , expDate } = this . state ;
2022-02-24 02:47:17 +00:00
let expirationTime = '' ;
2022-03-10 06:14:15 +00:00
if ( expType == 'by-days' ) {
2022-02-24 02:47:17 +00:00
expirationTime = moment ( ) . add ( parseInt ( expireDays ) , 'days' ) . format ( ) ;
} else {
expirationTime = expDate . format ( ) ;
}
seafileAPI . updateUploadLink ( this . state . sharedUploadInfo . token , expirationTime ) . then ( ( res ) => {
let sharedUploadInfo = new UploadLink ( res . data ) ;
this . setState ( {
2022-03-10 06:14:15 +00:00
sharedUploadInfo : sharedUploadInfo ,
isEditingExpiration : false ,
2022-02-24 02:47:17 +00:00
} ) ;
} ) . catch ( ( error ) => {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
} ) ;
}
2018-12-06 03:28:16 +00:00
deleteUploadLink = ( ) => {
2019-01-31 09:37:02 +00:00
let sharedUploadInfo = this . state . sharedUploadInfo ;
2019-01-04 10:11:13 +00:00
seafileAPI . deleteUploadLink ( sharedUploadInfo . token ) . then ( ( ) => {
2018-12-06 03:28:16 +00:00
this . setState ( {
2021-08-12 07:02:03 +00:00
showPasswordInput : shareLinkForceUsePassword ? true : false ,
2020-07-21 10:22:45 +00:00
expireDays : this . defaultExpireDays ,
2022-03-10 06:14:15 +00:00
expDate : null ,
2020-07-21 10:22:45 +00:00
isExpireChecked : ! this . isExpireDaysNoLimit ,
2018-12-06 03:28:16 +00:00
password : '' ,
passwordnew : '' ,
2019-01-04 10:11:13 +00:00
sharedUploadInfo : null ,
2018-12-06 03:28:16 +00:00
} ) ;
2019-07-16 02:01:09 +00:00
} ) . catch ( error => {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
2018-12-06 03:28:16 +00:00
} ) ;
}
2019-06-27 06:08:10 +00:00
toggleSendLink = ( ) => {
this . setState ( {
isSendLinkShown : ! this . state . isSendLinkShown
} ) ;
}
2018-12-06 03:28:16 +00:00
render ( ) {
2019-05-06 08:08:34 +00:00
2019-06-27 06:08:10 +00:00
const { isSendLinkShown } = this . state ;
2022-02-17 07:30:06 +00:00
let passwordLengthTip = gettext ( '(at least {passwordMinLength} characters and includes {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols)' ) ;
passwordLengthTip = passwordLengthTip . replace ( '{passwordMinLength}' , shareLinkPasswordMinLength )
2022-03-10 06:14:15 +00:00
. replace ( '{passwordStrengthLevel}' , shareLinkPasswordStrengthLevel ) ;
2021-08-12 07:02:03 +00:00
2019-01-04 10:11:13 +00:00
if ( this . state . sharedUploadInfo ) {
let sharedUploadInfo = this . state . sharedUploadInfo ;
2018-12-06 03:28:16 +00:00
return (
2019-01-04 10:11:13 +00:00
< div >
< Form className = "mb-4" >
< FormGroup >
< dt className = "text-secondary font-weight-normal" > { gettext ( 'Upload Link:' ) } < / d t >
2021-01-22 10:18:13 +00:00
< dd >
< SharedLink
link = { sharedUploadInfo . link }
linkExpired = { sharedUploadInfo . is _expired }
copyLink = { this . onCopyUploadLink }
/ >
2019-01-04 10:11:13 +00:00
< / d d >
< / F o r m G r o u p >
2021-11-23 04:09:08 +00:00
{ sharedUploadInfo . password && (
< FormGroup className = "mb-0" >
< dt className = "text-secondary font-weight-normal" > { gettext ( 'Password:' ) } < / d t >
< dd className = "d-flex" >
< div className = "d-flex align-items-center" >
< input id = "stored-password" className = "border-0 mr-1" type = "text" value = { this . state . storedPasswordVisible ? sharedUploadInfo . password : '****************************************' } readOnly = { true } size = { Math . max ( sharedUploadInfo . password . length , 10 ) } / >
< span tabIndex = "0" role = "button" aria - label = { this . state . storedPasswordVisible ? gettext ( 'Hide' ) : gettext ( 'Show' ) } onKeyDown = { this . onIconKeyDown } onClick = { this . toggleStoredPasswordVisible } className = { ` eye-icon fas ${ this . state . storedPasswordVisible ? 'fa-eye' : 'fa-eye-slash' } ` } > < / s p a n >
< / d i v >
< / d d >
< / F o r m G r o u p >
) }
2019-07-02 03:06:39 +00:00
{ sharedUploadInfo . expire _date && (
< FormGroup className = "mb-0" >
< dt className = "text-secondary font-weight-normal" > { gettext ( 'Expiration Date:' ) } < / d t >
2022-02-24 02:47:17 +00:00
{ ! this . state . isEditingExpiration &&
< dd style = { { width : '250px' } } onMouseEnter = { this . handleMouseOverExpirationEditIcon } onMouseLeave = { this . handleMouseOutExpirationEditIcon } >
2022-03-10 06:14:15 +00:00
{ moment ( sharedUploadInfo . expire _date ) . format ( 'YYYY-MM-DD HH:mm:ss' ) }
2022-02-24 02:47:17 +00:00
{ this . state . isExpirationEditIconShow && (
< a href = "#"
role = "button"
aria - label = { gettext ( 'Edit' ) }
title = { gettext ( 'Edit' ) }
className = "fa fa-pencil-alt attr-action-icon"
onClick = { this . editExpirationToggle } >
< / a >
) }
< / d d >
}
{ this . state . isEditingExpiration &&
< div className = "ml-4" >
2022-03-10 06:14:15 +00:00
< SetLinkExpiration
minDays = { uploadLinkExpireDaysMin }
maxDays = { uploadLinkExpireDaysMax }
defaultDays = { uploadLinkExpireDaysDefault }
expType = { this . state . expType }
setExpType = { this . setExpType }
expireDays = { this . state . expireDays }
onExpireDaysChanged = { this . onExpireDaysChanged }
expDate = { this . state . expDate }
onExpDateChanged = { this . onExpDateChanged }
/ >
< div className = { this . state . expType == 'by-days' ? 'mt-2' : 'mt-3' } >
< button className = "btn btn-primary mr-2" onClick = { this . updateExpiration } > { gettext ( 'Update' ) } < / b u t t o n >
< button className = "btn btn-secondary" onClick = { this . editExpirationToggle } > { gettext ( 'Cancel' ) } < / b u t t o n >
< / d i v >
2022-02-24 02:47:17 +00:00
< / d i v >
}
2019-07-02 03:06:39 +00:00
< / F o r m G r o u p >
) }
2019-01-04 10:11:13 +00:00
< / F o r m >
2019-06-27 06:08:10 +00:00
{ canSendShareLinkEmail && ! isSendLinkShown && < Button onClick = { this . toggleSendLink } className = "mr-2" > { gettext ( 'Send' ) } < / B u t t o n > }
{ ! isSendLinkShown && < Button onClick = { this . deleteUploadLink } > { gettext ( 'Delete' ) } < / B u t t o n > }
{ isSendLinkShown &&
< SendLink
linkType = 'uploadLink'
token = { sharedUploadInfo . token }
toggleSendLink = { this . toggleSendLink }
closeShareDialog = { this . props . closeShareDialog }
/ >
}
2019-01-04 10:11:13 +00:00
< / d i v >
2018-12-06 03:28:16 +00:00
) ;
}
return (
< Form className = "generate-upload-link" >
< FormGroup check >
< Label check >
2021-08-12 07:02:03 +00:00
{ shareLinkForceUsePassword ? (
2022-03-10 06:14:15 +00:00
< Label check >
< Input type = "checkbox" checked readOnly disabled / >
< span > { gettext ( 'Add password protection' ) } < / s p a n >
< / L a b e l >
2021-08-12 07:02:03 +00:00
) : (
2022-03-10 06:14:15 +00:00
< Label check >
< Input type = "checkbox" onChange = { this . addPassword } / >
< span > { gettext ( 'Add password protection' ) } < / s p a n >
< / L a b e l >
2021-08-12 07:02:03 +00:00
) }
2018-12-06 03:28:16 +00:00
< / L a b e l >
2020-05-21 03:32:02 +00:00
{ this . state . showPasswordInput &&
< div className = "ml-4" >
< FormGroup >
< Label for = "passwd" > { gettext ( 'Password' ) } < / L a b e l >
< span className = "tip" > { passwordLengthTip } < / s p a n >
< InputGroup style = { { width : inputWidth } } >
< Input id = "passwd" type = { this . state . passwordVisible ? 'text' : 'password' } value = { this . state . password || '' } onChange = { this . inputPassword } / >
< InputGroupAddon addonType = "append" >
< Button onClick = { this . togglePasswordVisible } > < i className = { ` link-operation-icon fas ${ this . state . passwordVisible ? 'fa-eye' : 'fa-eye-slash' } ` } > < / i > < / B u t t o n >
< Button onClick = { this . generatePassword } > < i className = "link-operation-icon fas fa-magic" > < / i > < / B u t t o n >
< / I n p u t G r o u p A d d o n >
< / I n p u t G r o u p >
< / F o r m G r o u p >
< FormGroup >
< Label for = "passwd-again" > { gettext ( 'Password again' ) } < / L a b e l >
< Input id = "passwd-again" style = { { width : inputWidth } } type = { this . state . passwordVisible ? 'text' : 'password' } value = { this . state . passwordnew || '' } onChange = { this . inputPasswordNew } / >
< / F o r m G r o u p >
< / d i v >
}
2018-12-06 03:28:16 +00:00
< / F o r m G r o u p >
2019-07-02 03:06:39 +00:00
< FormGroup check >
< Label check >
2020-11-02 05:56:35 +00:00
{ this . isExpireDaysNoLimit ? (
< Input type = "checkbox" onChange = { this . onExpireChecked } / >
) : (
< Input type = "checkbox" checked readOnly disabled / >
) }
2020-05-21 03:32:02 +00:00
< span > { gettext ( 'Add auto expiration' ) } < / s p a n >
2019-07-02 03:06:39 +00:00
< / L a b e l >
2020-05-21 03:32:02 +00:00
{ this . state . isExpireChecked &&
< div className = "ml-4" >
2022-03-10 06:14:15 +00:00
< SetLinkExpiration
minDays = { uploadLinkExpireDaysMin }
maxDays = { uploadLinkExpireDaysMax }
defaultDays = { uploadLinkExpireDaysDefault }
expType = { this . state . expType }
setExpType = { this . setExpType }
expireDays = { this . state . expireDays }
onExpireDaysChanged = { this . onExpireDaysChanged }
expDate = { this . state . expDate }
onExpDateChanged = { this . onExpDateChanged }
/ >
2020-05-21 03:32:02 +00:00
< / d i v >
}
2019-07-02 03:06:39 +00:00
< / F o r m G r o u p >
2019-02-12 03:16:49 +00:00
{ this . state . errorInfo && < Alert color = "danger" className = "mt-2" > { this . state . errorInfo } < / A l e r t > }
2018-12-06 03:28:16 +00:00
< Button className = "generate-link-btn" onClick = { this . generateUploadLink } > { gettext ( 'Generate' ) } < / B u t t o n >
< / F o r m >
) ;
}
}
GenerateUploadLink . propTypes = propTypes ;
export default GenerateUploadLink ;