2023-04-04 16:12:53 +08:00
import React , { Fragment } from 'react' ;
import PropTypes from 'prop-types' ;
import moment from 'moment' ;
import { Button , Form , FormGroup , Label , Input , InputGroup , InputGroupAddon , Alert } from 'reactstrap' ;
2024-07-11 14:28:00 +08:00
import { gettext , shareLinkExpireDaysMin , shareLinkExpireDaysMax , shareLinkExpireDaysDefault , shareLinkForceUsePassword , shareLinkPasswordMinLength , shareLinkPasswordStrengthLevel } from '../../utils/constants' ;
2023-04-04 16:12:53 +08:00
import { seafileAPI } from '../../utils/seafile-api' ;
import { Utils } from '../../utils/utils' ;
import ShareLink from '../../models/share-link' ;
import toaster from '../toast' ;
import SetLinkExpiration from '../set-link-expiration' ;
const propTypes = {
itemPath : PropTypes . string . isRequired ,
repoID : PropTypes . string . isRequired ,
userPerm : PropTypes . string ,
type : PropTypes . string . isRequired ,
permissionOptions : PropTypes . array . isRequired ,
currentPermission : PropTypes . string . isRequired ,
updateAfterCreation : PropTypes . func . isRequired ,
setMode : PropTypes . func . isRequired ,
} ;
const inputWidth = Utils . isDesktop ( ) ? 250 : 210 ;
2023-08-22 17:35:16 +08:00
const SHARE _LINK _MAX _NUMBER = 200 ;
2023-04-04 16:12:53 +08:00
class LinkCreation extends React . Component {
constructor ( props ) {
super ( props ) ;
this . isExpireDaysNoLimit = ( shareLinkExpireDaysMin === 0 && shareLinkExpireDaysMax === 0 && shareLinkExpireDaysDefault == 0 ) ;
this . defaultExpireDays = this . isExpireDaysNoLimit ? '' : shareLinkExpireDaysDefault ;
this . state = {
linkAmount : '' ,
isShowPasswordInput : shareLinkForceUsePassword ? true : false ,
isPasswordVisible : false ,
isExpireChecked : ! this . isExpireDaysNoLimit ,
expType : 'by-days' ,
expireDays : this . defaultExpireDays ,
expDate : null ,
password : '' ,
passwdnew : '' ,
errorInfo : '' ,
currentPermission : props . currentPermission
} ;
}
setExpType = ( e ) => {
this . setState ( {
expType : e . target . value
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onExpDateChanged = ( value ) => {
this . setState ( {
expDate : value
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onPasswordInputChecked = ( ) => {
this . setState ( {
isShowPasswordInput : ! this . state . isShowPasswordInput ,
password : '' ,
passwdnew : '' ,
errorInfo : ''
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
togglePasswordVisible = ( ) => {
this . setState ( {
isPasswordVisible : ! this . state . isPasswordVisible
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
generatePassword = ( ) => {
let val = Utils . generatePassword ( shareLinkPasswordMinLength ) ;
this . setState ( {
password : val ,
passwdnew : val
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
inputPassword = ( e ) => {
let passwd = e . target . value . trim ( ) ;
this . setState ( { password : passwd } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
inputPasswordNew = ( e ) => {
let passwd = e . target . value . trim ( ) ;
this . setState ( { passwdnew : passwd } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
setPermission = ( e ) => {
this . setState ( { currentPermission : e . target . value } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
generateShareLink = ( ) => {
let isValid = this . validateParamsInput ( ) ;
if ( isValid ) {
this . setState ( { errorInfo : '' } ) ;
let { type , itemPath , repoID } = this . props ;
let { linkAmount , isShowPasswordInput , password , isExpireChecked , expType , expireDays , expDate } = this . state ;
2024-07-11 14:28:00 +08:00
const permissionDetails = Utils . getShareLinkPermissionObject ( this . state . currentPermission ) . permissionDetails ;
2023-04-04 16:12:53 +08:00
let permissions ;
2024-07-11 14:28:00 +08:00
permissions = JSON . stringify ( permissionDetails ) ;
2023-04-04 16:12:53 +08:00
let expirationTime = '' ;
if ( isExpireChecked ) {
if ( expType == 'by-days' ) {
expirationTime = moment ( ) . add ( parseInt ( expireDays ) , 'days' ) . format ( ) ;
} else {
expirationTime = expDate . format ( ) ;
}
}
let request ;
if ( type == 'batch' ) {
const autoGeneratePassword = shareLinkForceUsePassword || isShowPasswordInput ;
request = seafileAPI . batchCreateMultiShareLink ( repoID , itemPath , linkAmount , autoGeneratePassword , expirationTime , permissions ) ;
} else {
request = seafileAPI . createMultiShareLink ( repoID , itemPath , password , expirationTime , permissions ) ;
}
request . then ( ( res ) => {
if ( type == 'batch' ) {
const newLinks = res . data . map ( item => new ShareLink ( item ) ) ;
this . props . updateAfterCreation ( newLinks ) ;
} else {
const newLink = new ShareLink ( res . data ) ;
this . props . updateAfterCreation ( newLink ) ;
}
} ) . catch ( ( error ) => {
2024-03-18 11:28:01 +08:00
let resp _data = error . response . data ;
let errMessage = resp _data && resp _data [ 'error_msg' ] ;
if ( errMessage === 'Folder permission denied.' ) {
2024-04-15 19:31:56 +08:00
this . setState ( { errorInfo : gettext ( 'Share links cannot be generated because "Invisible", "Online Read-Write" or "Online Read-Only" is set for you on some folder(s) in the library.' ) } ) ;
2024-03-18 11:28:01 +08:00
} else {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
}
2023-04-04 16:12:53 +08:00
} ) ;
}
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onExpireChecked = ( e ) => {
this . setState ( { isExpireChecked : e . target . checked } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onExpireDaysChanged = ( e ) => {
let day = e . target . value . trim ( ) ;
this . setState ( { expireDays : day } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
validateParamsInput = ( ) => {
const { type } = this . props ;
let { linkAmount , isShowPasswordInput , password , passwdnew , isExpireChecked , expType , expireDays , expDate } = this . state ;
if ( type == 'batch' ) {
if ( ! Number . isInteger ( parseInt ( linkAmount ) ) || parseInt ( linkAmount ) <= 1 ) {
this . setState ( { errorInfo : gettext ( 'Please enter an integer bigger than 1 as number of links.' ) } ) ;
return false ;
}
2023-08-22 17:35:16 +08:00
if ( parseInt ( linkAmount ) > SHARE _LINK _MAX _NUMBER ) {
2023-08-25 18:04:38 +08:00
this . setState ( { errorInfo : gettext ( 'Please enter an integer not bigger than {max_number} as number of links.' ) . replace ( '{max_number}' , SHARE _LINK _MAX _NUMBER ) } ) ;
2023-08-22 16:44:06 +08:00
return false ;
}
2023-04-04 16:12:53 +08:00
}
2023-04-04 16:43:54 +08:00
if ( type == 'single' && isShowPasswordInput ) {
2023-04-04 16:12:53 +08:00
if ( password . length === 0 ) {
this . setState ( { errorInfo : gettext ( 'Please enter a password.' ) } ) ;
return false ;
}
if ( password . length < shareLinkPasswordMinLength ) {
this . setState ( { errorInfo : gettext ( 'The password is too short.' ) } ) ;
return false ;
}
if ( password !== passwdnew ) {
this . setState ( { errorInfo : gettext ( 'Passwords don\'t match' ) } ) ;
return false ;
}
if ( Utils . getStrengthLevel ( password ) < shareLinkPasswordStrengthLevel ) {
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 ) } ) ;
return false ;
}
}
if ( isExpireChecked ) {
if ( expType == 'by-date' ) {
if ( ! expDate ) {
this . setState ( { errorInfo : gettext ( 'Please select an expiration time' ) } ) ;
return false ;
}
return true ;
}
// by days
let reg = /^\d+$/ ;
if ( ! expireDays ) {
this . setState ( { errorInfo : gettext ( 'Please enter days' ) } ) ;
return false ;
}
if ( ! reg . test ( expireDays ) ) {
this . setState ( { errorInfo : gettext ( 'Please enter a non-negative integer' ) } ) ;
return false ;
}
expireDays = parseInt ( expireDays ) ;
let minDays = shareLinkExpireDaysMin ;
let maxDays = shareLinkExpireDaysMax ;
if ( minDays !== 0 && maxDays == 0 ) {
if ( expireDays < minDays ) {
this . setState ( { errorInfo : 'Please enter valid days' } ) ;
return false ;
}
}
if ( minDays === 0 && maxDays !== 0 ) {
if ( expireDays > maxDays ) {
this . setState ( { errorInfo : 'Please enter valid days' } ) ;
return false ;
}
}
if ( minDays !== 0 && maxDays !== 0 ) {
if ( expireDays < minDays || expireDays > maxDays ) {
this . setState ( { errorInfo : 'Please enter valid days' } ) ;
return false ;
}
}
this . setState ( { expireDays : expireDays } ) ;
}
return true ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onLinkAmountChange = ( e ) => {
this . setState ( {
linkAmount : e . target . value
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
goBack = ( ) => {
this . props . setMode ( '' ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
render ( ) {
const { userPerm , type , permissionOptions } = this . props ;
const { isCustomPermission } = Utils . getUserPermission ( userPerm ) ;
return (
< Fragment >
< div className = "d-flex align-items-center pb-2 border-bottom" >
< h6 className = "font-weight-normal m-0" >
2024-06-28 08:39:44 +08:00
< button className = "sf3-font sf3-font-arrow rotate-180 d-inline-block back-icon border-0 bg-transparent text-secondary p-0 mr-2" onClick = { this . goBack } title = { gettext ( 'Back' ) } aria - label = { gettext ( 'Back' ) } > < / b u t t o n >
2023-04-04 16:12:53 +08:00
{ type == 'batch' ? gettext ( 'Generate links in batch' ) : gettext ( 'Generate Link' ) } < / h 6 >
< / d i v >
2023-04-10 15:57:39 +08:00
< Form className = "pt-4" >
2023-04-04 16:12:53 +08:00
{ type == 'batch' && (
< FormGroup >
2023-04-10 15:57:39 +08:00
< Label for = "link-number" className = "p-0" > { gettext ( 'Number of links' ) } < / L a b e l >
< Input type = "number" id = "link-number" value = { this . state . linkAmount } onChange = { this . onLinkAmountChange } style = { { width : inputWidth } } / >
2023-04-04 16:12:53 +08:00
< / F o r m G r o u p >
) }
< FormGroup check >
{ shareLinkForceUsePassword ? (
< Label check >
< Input type = "checkbox" checked readOnly disabled / >
< span > { gettext ( 'Add password protection' ) } < / s p a n >
< / L a b e l >
) : (
< Label check >
< Input type = "checkbox" checked = { this . state . isShowPasswordInput } onChange = { this . onPasswordInputChecked } / >
< span > { gettext ( 'Add password protection' ) } < / s p a n >
< / L a b e l >
) }
{ type != 'batch' && this . state . isShowPasswordInput &&
< div className = "ml-4" >
< FormGroup >
< Label for = "passwd" > { gettext ( 'Password' ) } < / L a b e l >
< span className = "tip" > { gettext ( '(at least {passwordMinLength} characters and includes {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols)' ) . replace ( '{passwordMinLength}' , shareLinkPasswordMinLength ) . replace ( '{passwordStrengthLevel}' , shareLinkPasswordStrengthLevel ) } < / s p a n >
< InputGroup style = { { width : inputWidth } } >
< Input id = "passwd" type = { this . state . isPasswordVisible ? 'text' : 'password' } value = { this . state . password || '' } onChange = { this . inputPassword } / >
< InputGroupAddon addonType = "append" >
2024-06-28 08:39:44 +08:00
< Button onClick = { this . togglePasswordVisible } >
< i className = { ` link-operation-icon sf3-font sf3-font-eye ${ this . state . isPasswordVisible ? '' : '-slash' } ` } > < / i >
< / B u t t o n >
< Button onClick = { this . generatePassword } >
< i className = "link-operation-icon sf3-font sf3-font-magic" > < / i >
< / B u t t o n >
2023-04-04 16:12:53 +08:00
< / 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 . isPasswordVisible ? 'text' : 'password' } value = { this . state . passwdnew || '' } onChange = { this . inputPasswordNew } / >
< / F o r m G r o u p >
< / d i v >
}
< / F o r m G r o u p >
< FormGroup check >
< Label check >
{ this . isExpireDaysNoLimit ? (
< Input type = "checkbox" onChange = { this . onExpireChecked } / >
) : (
< Input type = "checkbox" checked readOnly disabled / >
) }
< span > { gettext ( 'Add auto expiration' ) } < / s p a n >
< / L a b e l >
{ this . state . isExpireChecked &&
< div className = "ml-4" >
< SetLinkExpiration
minDays = { shareLinkExpireDaysMin }
maxDays = { shareLinkExpireDaysMax }
defaultDays = { shareLinkExpireDaysDefault }
expType = { this . state . expType }
setExpType = { this . setExpType }
expireDays = { this . state . expireDays }
onExpireDaysChanged = { this . onExpireDaysChanged }
expDate = { this . state . expDate }
onExpDateChanged = { this . onExpDateChanged }
/ >
< / d i v >
}
< / F o r m G r o u p >
2024-07-11 14:28:00 +08:00
{ ! isCustomPermission && (
2023-04-04 16:12:53 +08:00
< FormGroup check >
< Label check >
< span > { gettext ( 'Set permission' ) } < / s p a n >
< / L a b e l >
{ permissionOptions . map ( ( item , index ) => {
return (
< FormGroup check className = "ml-4" key = { index } >
< Label check >
< Input type = "radio" name = "permission" value = { item } checked = { this . state . currentPermission == item } onChange = { this . setPermission } className = "mr-1" / >
{ Utils . getShareLinkPermissionObject ( item ) . text }
< / L a b e l >
< / F o r m G r o u p >
) ;
} ) }
< / F o r m G r o u p >
) }
{ this . state . errorInfo && < Alert color = "danger" className = "mt-2" > { gettext ( this . state . errorInfo ) } < / A l e r t > }
2023-11-30 17:31:22 +08:00
< Button onClick = { this . generateShareLink } className = "mt-2 ml-1 mb-1" > { gettext ( 'Generate' ) } < / B u t t o n >
2023-04-04 16:12:53 +08:00
< / F o r m >
< / F r a g m e n t >
) ;
}
}
LinkCreation . propTypes = propTypes ;
export default LinkCreation ;