2018-12-27 10:24:34 +08:00
import React , { Component , Fragment } from 'react' ;
2018-11-19 11:53:44 +08:00
import { Link } from '@reach/router' ;
import moment from 'moment' ;
2019-08-20 12:00:58 +08:00
import { Dropdown , DropdownToggle , DropdownItem } from 'reactstrap' ;
2018-11-19 11:53:44 +08:00
import { seafileAPI } from '../../utils/seafile-api' ;
import { Utils } from '../../utils/utils' ;
2019-12-05 15:45:16 +08:00
import { isPro , gettext , siteRoot , canGenerateUploadLink } from '../../utils/constants' ;
2019-08-20 12:00:58 +08:00
import ShareLink from '../../models/share-link' ;
import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor' ;
import Loading from '../../components/loading' ;
2019-02-20 10:40:54 +08:00
import toaster from '../../components/toast' ;
2019-06-10 17:30:10 +08:00
import EmptyTip from '../../components/empty-tip' ;
2019-08-20 12:00:58 +08:00
import ShareLinkPermissionSelect from '../../components/dialog/share-link-permission-select' ;
2019-08-21 16:36:55 +08:00
import ShareAdminLink from '../../components/dialog/share-admin-link' ;
2018-11-19 11:53:44 +08:00
class Content extends Component {
2018-12-26 16:07:22 +08:00
2019-01-02 17:49:30 +08:00
sortByName = ( e ) => {
e . preventDefault ( ) ;
const sortBy = 'name' ;
const sortOrder = this . props . sortOrder == 'asc' ? 'desc' : 'asc' ;
this . props . sortItems ( sortBy , sortOrder ) ;
}
sortByTime = ( e ) => {
e . preventDefault ( ) ;
const sortBy = 'time' ;
const sortOrder = this . props . sortOrder == 'asc' ? 'desc' : 'asc' ;
this . props . sortItems ( sortBy , sortOrder ) ;
}
2018-11-19 11:53:44 +08:00
render ( ) {
2019-01-02 17:49:30 +08:00
const { loading , errorMsg , items , sortBy , sortOrder } = this . props ;
2018-11-19 11:53:44 +08:00
if ( loading ) {
2019-08-20 12:00:58 +08:00
return < Loading / > ;
2018-11-19 11:53:44 +08:00
} else if ( errorMsg ) {
return < p className = "error text-center" > { errorMsg } < / p > ;
} else {
const emptyTip = (
2019-06-10 17:30:10 +08:00
< EmptyTip >
2020-01-03 16:50:17 +08:00
< h2 > { gettext ( 'No share links' ) } < / h 2 >
< p > { gettext ( 'You have not created any share links yet. A share link can be used to share files and folders with anyone. You can create a share link for a file or folder by clicking the share icon to the right of its name.' ) } < / p >
2019-06-10 17:30:10 +08:00
< / E m p t y T i p >
2018-11-19 11:53:44 +08:00
) ;
2019-01-02 17:49:30 +08:00
// sort
const sortByName = sortBy == 'name' ;
const sortByTime = sortBy == 'time' ;
const sortIcon = sortOrder == 'asc' ? < span className = "fas fa-caret-up" > < / s p a n > : < s p a n c l a s s N a m e = " f a s f a - c a r e t - d o w n " > < / s p a n > ;
2019-08-20 12:00:58 +08:00
const isDesktop = Utils . isDesktop ( ) ;
// only for some columns
const columnWidths = isPro ? [ '14%' , '7%' , '14%' ] : [ '21%' , '14%' , '20%' ] ;
2018-11-19 11:53:44 +08:00
const table = (
2019-08-21 16:36:55 +08:00
< table className = { ` table-hover ${ isDesktop ? '' : 'table-thead-hidden' } ` } >
< thead >
{ isDesktop ? (
< tr >
< th width = "4%" > { /*icon*/ } < / t h >
< th width = "31%" > < a className = "d-block table-sort-op" href = "#" onClick = { this . sortByName } > { gettext ( 'Name' ) } { sortByName && sortIcon } < / a > < / t h >
< th width = { columnWidths [ 0 ] } > { gettext ( 'Library' ) } < / t h >
{ isPro && < th width = "20%" > { gettext ( 'Permission' ) } < / t h > }
< th width = { columnWidths [ 1 ] } > { gettext ( 'Visits' ) } < / t h >
< th width = { columnWidths [ 2 ] } > < a className = "d-block table-sort-op" href = "#" onClick = { this . sortByTime } > { gettext ( 'Expiration' ) } { sortByTime && sortIcon } < / a > < / t h >
< th width = "10%" > { /*Operations*/ } < / t h >
< / t r >
2020-11-02 13:56:35 +08:00
) : (
2019-08-21 16:36:55 +08:00
< tr >
< th width = "12%" > < / t h >
< th width = "80%" > < / t h >
< th width = "8%" > < / t h >
< / t r >
2020-11-02 13:56:35 +08:00
) }
2019-08-21 16:36:55 +08:00
< / t h e a d >
< tbody >
{ items . map ( ( item , index ) => {
return ( < Item key = { index } isDesktop = { isDesktop } item = { item } onRemoveLink = { this . props . onRemoveLink } / > ) ;
} ) }
< / t b o d y >
< / t a b l e >
2018-11-19 11:53:44 +08:00
) ;
2020-11-02 13:56:35 +08:00
return items . length ? table : emptyTip ;
2018-11-19 11:53:44 +08:00
}
}
}
class Item extends Component {
constructor ( props ) {
super ( props ) ;
2019-04-17 14:06:21 +08:00
2018-11-19 11:53:44 +08:00
this . state = {
2019-08-20 12:00:58 +08:00
isOpIconShown : false ,
isOpMenuOpen : false , // for mobile
2019-08-21 16:36:55 +08:00
isPermSelectDialogOpen : false , // for mobile
2019-10-12 14:54:25 +08:00
isLinkDialogOpen : false ,
permissionOptions : [ ] ,
currentPermission : '' ,
2018-11-19 11:53:44 +08:00
} ;
2019-08-20 12:00:58 +08:00
}
2019-10-12 14:54:25 +08:00
componentDidMount ( ) {
if ( isPro ) {
this . updatePermissionOptions ( ) ;
2019-08-20 12:00:58 +08:00
}
2019-10-12 14:54:25 +08:00
}
2019-08-20 12:00:58 +08:00
2019-10-12 14:54:25 +08:00
updatePermissionOptions = ( ) => {
const item = this . props . item ;
2019-12-03 13:52:52 +08:00
let itemType = item . is _dir ? ( item . path === '/' ? 'library' : 'dir' ) : 'file' ;
let permission = item . repo _folder _permission ;
let permissionOptions = Utils . getShareLinkPermissionList ( itemType , permission , item . path , item . can _edit ) ;
2020-01-02 12:05:50 +08:00
let currentPermission = Utils . getShareLinkPermissionStr ( this . props . item . permissions ) ;
2019-10-12 14:54:25 +08:00
this . setState ( {
2019-12-03 13:52:52 +08:00
permissionOptions : permissionOptions ,
currentPermission : currentPermission
2019-08-20 12:00:58 +08:00
} ) ;
}
toggleOpMenu = ( ) => {
this . setState ( {
isOpMenuOpen : ! this . state . isOpMenuOpen
2020-11-02 13:56:35 +08:00
} ) ;
2019-08-20 12:00:58 +08:00
}
togglePermSelectDialog = ( ) => {
this . setState ( {
isPermSelectDialogOpen : ! this . state . isPermSelectDialogOpen
2020-11-02 13:56:35 +08:00
} ) ;
2018-11-19 11:53:44 +08:00
}
2019-08-21 16:36:55 +08:00
toggleLinkDialog = ( ) => {
this . setState ( {
isLinkDialogOpen : ! this . state . isLinkDialogOpen
2020-11-02 13:56:35 +08:00
} ) ;
2019-08-21 16:36:55 +08:00
}
2018-12-26 16:07:22 +08:00
handleMouseOver = ( ) => {
2019-08-20 12:00:58 +08:00
this . setState ( { isOpIconShown : true } ) ;
2018-11-19 11:53:44 +08:00
}
2018-12-26 16:07:22 +08:00
handleMouseOut = ( ) => {
2019-08-20 12:00:58 +08:00
this . setState ( { isOpIconShown : false } ) ;
2018-11-19 11:53:44 +08:00
}
2018-12-26 16:07:22 +08:00
viewLink = ( e ) => {
2018-11-19 11:53:44 +08:00
e . preventDefault ( ) ;
2019-08-21 16:36:55 +08:00
this . toggleLinkDialog ( ) ;
2018-11-19 11:53:44 +08:00
}
2020-11-02 13:56:35 +08:00
2018-12-26 16:07:22 +08:00
removeLink = ( e ) => {
2018-11-19 11:53:44 +08:00
e . preventDefault ( ) ;
2018-12-27 10:24:34 +08:00
this . props . onRemoveLink ( this . props . item ) ;
2018-11-19 11:53:44 +08:00
}
2019-08-21 16:36:55 +08:00
renderExpiration = ( ) => {
2020-05-25 17:29:16 +08:00
const item = this . props . item ;
2018-12-27 10:24:34 +08:00
if ( ! item . expire _date ) {
2020-05-25 17:29:16 +08:00
return '--' ;
2018-11-19 11:53:44 +08:00
}
2020-05-25 17:29:16 +08:00
const expire _date = moment ( item . expire _date ) . format ( 'YYYY-MM-DD' ) ;
const expire _time = moment ( item . expire _date ) . format ( 'YYYY-MM-DD HH:mm:ss' ) ;
return ( < span className = { item . is _expired ? 'error' : '' } title = { expire _time } > { expire _date } < / s p a n > ) ;
2018-12-27 10:24:34 +08:00
}
2019-08-20 12:00:58 +08:00
changePerm = ( permission ) => {
2019-04-17 14:06:21 +08:00
const item = this . props . item ;
2019-08-20 12:00:58 +08:00
const permissionDetails = Utils . getShareLinkPermissionObject ( permission ) . permissionDetails ;
seafileAPI . updateShareLink ( item . token , JSON . stringify ( permissionDetails ) ) . then ( ( ) => {
2019-04-17 14:06:21 +08:00
this . setState ( {
2020-11-02 13:56:35 +08:00
currentPermission : permission
2019-04-17 14:06:21 +08:00
} ) ;
2019-08-20 12:00:58 +08:00
let message = gettext ( 'Successfully modified permission.' ) ;
2019-07-16 10:01:09 +08:00
toaster . success ( message ) ;
2019-04-17 14:06:21 +08:00
} ) . catch ( ( error ) => {
2019-07-16 10:01:09 +08:00
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
2019-04-17 14:06:21 +08:00
} ) ;
}
2018-12-27 10:24:34 +08:00
render ( ) {
const item = this . props . item ;
2019-10-12 14:54:25 +08:00
const { currentPermission , permissionOptions , isOpIconShown , isPermSelectDialogOpen , isLinkDialogOpen } = this . state ;
2018-11-19 11:53:44 +08:00
2019-08-21 16:36:55 +08:00
let iconUrl , objUrl ;
2019-08-20 12:00:58 +08:00
if ( item . is _dir ) {
let path = item . path === '/' ? '/' : item . path . slice ( 0 , item . path . length - 1 ) ;
iconUrl = Utils . getFolderIconUrl ( false ) ;
2019-08-21 16:36:55 +08:00
objUrl = ` ${ siteRoot } library/ ${ item . repo _id } / ${ encodeURIComponent ( item . repo _name ) } ${ Utils . encodePath ( path ) } ` ;
2019-08-20 12:00:58 +08:00
} else {
2020-11-02 13:56:35 +08:00
iconUrl = Utils . getFileIconUrl ( item . obj _name ) ;
2019-08-21 16:36:55 +08:00
objUrl = ` ${ siteRoot } lib/ ${ item . repo _id } /file ${ Utils . encodePath ( item . path ) } ` ;
2019-08-20 12:00:58 +08:00
}
const desktopItem = (
2021-09-24 17:11:14 +08:00
< tr onMouseEnter = { this . handleMouseOver } onMouseLeave = { this . handleMouseOut } onFocus = { this . handleMouseOver } >
2019-08-20 12:00:58 +08:00
< td > < img src = { iconUrl } width = "24" alt = "" / > < / t d >
2019-01-02 11:52:44 +08:00
< td >
{ item . is _dir ?
2019-08-21 16:36:55 +08:00
< Link to = { objUrl } > { item . obj _name } < / L i n k > :
< a href = { objUrl } target = "_blank" > { item . obj _name } < / a >
2019-01-02 11:52:44 +08:00
}
< / t d >
2019-08-20 12:00:58 +08:00
< td > < Link to = { ` ${ siteRoot } library/ ${ item . repo _id } / ${ encodeURIComponent ( item . repo _name ) } / ` } > { item . repo _name } < / L i n k > < / t d >
{ isPro &&
2019-04-17 14:06:21 +08:00
< td >
2020-11-02 13:56:35 +08:00
< ShareLinkPermissionEditor
2019-04-17 14:06:21 +08:00
isTextMode = { true }
2019-08-20 12:00:58 +08:00
isEditIconShow = { isOpIconShown && ! item . is _expired }
currentPermission = { currentPermission }
2019-10-12 14:54:25 +08:00
permissionOptions = { permissionOptions }
2019-04-17 14:06:21 +08:00
onPermissionChanged = { this . changePerm }
/ >
< / t d >
2019-08-20 12:00:58 +08:00
}
2018-12-27 10:24:34 +08:00
< td > { item . view _cnt } < / t d >
2020-11-02 13:56:35 +08:00
< td > { this . renderExpiration ( ) } < / t d >
2018-11-19 11:53:44 +08:00
< td >
2021-09-24 17:11:14 +08:00
{ ! item . is _expired && < a href = "#" className = { ` sf2-icon-link action-icon ${ isOpIconShown ? '' : 'invisible' } ` } title = { gettext ( 'View' ) } aria - label = { gettext ( 'View' ) } role = "button" onClick = { this . viewLink } > < / a > }
< a href = "#" className = { ` sf2-icon-delete action-icon ${ isOpIconShown ? '' : 'invisible' } ` } title = { gettext ( 'Remove' ) } aria - label = { gettext ( 'Remove' ) } role = "button" onClick = { this . removeLink } > < / a >
2018-11-19 11:53:44 +08:00
< / t d >
< / t r >
) ;
2019-08-20 12:00:58 +08:00
2020-11-02 13:56:35 +08:00
const mobileItem = (
2019-08-20 12:00:58 +08:00
< Fragment >
< tr >
< td > < img src = { iconUrl } alt = "" width = "24" / > < / t d >
< td >
{ item . is _dir ?
2019-08-21 16:36:55 +08:00
< Link to = { objUrl } > { item . obj _name } < / L i n k > :
< a href = { objUrl } target = "_blank" > { item . obj _name } < / a >
2019-08-20 12:00:58 +08:00
}
{ isPro && < span className = "item-meta-info-highlighted" > { Utils . getShareLinkPermissionObject ( currentPermission ) . text } < / s p a n > }
< br / >
< span > { item . repo _name } < /span><br / >
< span className = "item-meta-info" > { item . view _cnt } < span className = "small text-secondary" > ( { gettext ( 'Visits' ) } ) < / s p a n > < / s p a n >
2019-08-21 16:36:55 +08:00
< span className = "item-meta-info" > { this . renderExpiration ( ) } < span className = "small text-secondary" > ( { gettext ( 'Expiration' ) } ) < / s p a n > < / s p a n >
2019-08-20 12:00:58 +08:00
< / t d >
< td >
< Dropdown isOpen = { this . state . isOpMenuOpen } toggle = { this . toggleOpMenu } >
< DropdownToggle
tag = "i"
className = "sf-dropdown-toggle fa fa-ellipsis-v ml-0"
title = { gettext ( 'More Operations' ) }
data - toggle = "dropdown"
aria - expanded = { this . state . isOpMenuOpen }
2020-11-02 13:56:35 +08:00
/ >
2019-08-20 12:00:58 +08:00
< div className = { this . state . isOpMenuOpen ? '' : 'd-none' } onClick = { this . toggleOpMenu } >
< div className = "mobile-operation-menu-bg-layer" > < / d i v >
< div className = "mobile-operation-menu" >
{ ( isPro && ! item . is _expired ) && < DropdownItem className = "mobile-menu-item" onClick = { this . togglePermSelectDialog } > { gettext ( 'Permission' ) } < / D r o p d o w n I t e m > }
2019-08-21 16:36:55 +08:00
{ ! item . is _expired && < DropdownItem className = "mobile-menu-item" onClick = { this . viewLink } > { gettext ( 'View' ) } < / D r o p d o w n I t e m > }
2019-08-20 12:00:58 +08:00
< DropdownItem className = "mobile-menu-item" onClick = { this . removeLink } > { gettext ( 'Remove' ) } < / D r o p d o w n I t e m >
< / d i v >
< / d i v >
< / D r o p d o w n >
< / t d >
< / t r >
{ isPermSelectDialogOpen &&
< ShareLinkPermissionSelect
currentPerm = { currentPermission }
2019-10-12 14:54:25 +08:00
permissions = { permissionOptions }
2019-08-20 12:00:58 +08:00
changePerm = { this . changePerm }
toggleDialog = { this . togglePermSelectDialog }
/ >
}
< / F r a g m e n t >
) ;
2019-08-21 16:36:55 +08:00
return (
< Fragment >
{ this . props . isDesktop ? desktopItem : mobileItem }
{ isLinkDialogOpen &&
< ShareAdminLink
link = { item . link }
toggleDialog = { this . toggleLinkDialog }
/ >
}
< / F r a g m e n t >
) ;
2018-11-19 11:53:44 +08:00
}
}
class ShareAdminShareLinks extends Component {
2018-12-26 16:07:22 +08:00
2018-11-19 11:53:44 +08:00
constructor ( props ) {
super ( props ) ;
this . state = {
loading : true ,
errorMsg : '' ,
2019-01-02 17:49:30 +08:00
items : [ ] ,
sortBy : 'name' , // 'name' or 'time'
sortOrder : 'asc' // 'asc' or 'desc'
2018-11-19 11:53:44 +08:00
} ;
}
2019-01-02 17:49:30 +08:00
_sortItems = ( items , sortBy , sortOrder ) => {
let comparator ;
switch ( ` ${ sortBy } - ${ sortOrder } ` ) {
case 'name-asc' :
comparator = function ( a , b ) {
var result = Utils . compareTwoWord ( a . obj _name , b . obj _name ) ;
return result ;
} ;
break ;
case 'name-desc' :
comparator = function ( a , b ) {
var result = Utils . compareTwoWord ( a . obj _name , b . obj _name ) ;
return - result ;
} ;
break ;
case 'time-asc' :
comparator = function ( a , b ) {
return a . expire _date < b . expire _date ? - 1 : 1 ;
} ;
break ;
case 'time-desc' :
comparator = function ( a , b ) {
return a . expire _date < b . expire _date ? 1 : - 1 ;
} ;
break ;
}
items . sort ( ( a , b ) => {
if ( a . is _dir && ! b . is _dir ) {
return - 1 ;
} else if ( ! a . is _dir && b . is _dir ) {
return 1 ;
} else {
return comparator ( a , b ) ;
}
} ) ;
return items ;
}
sortItems = ( sortBy , sortOrder ) => {
this . setState ( {
sortBy : sortBy ,
sortOrder : sortOrder ,
items : this . _sortItems ( this . state . items , sortBy , sortOrder )
} ) ;
}
2018-11-19 11:53:44 +08:00
componentDidMount ( ) {
2020-01-11 13:29:04 +08:00
seafileAPI . listUserShareLinks ( ) . then ( ( res ) => {
2018-12-27 10:24:34 +08:00
let items = res . data . map ( item => {
2019-08-20 12:00:58 +08:00
return new ShareLink ( item ) ;
2018-12-27 10:24:34 +08:00
} ) ;
2018-11-19 11:53:44 +08:00
this . setState ( {
loading : false ,
2019-01-02 17:49:30 +08:00
items : this . _sortItems ( items , this . state . sortBy , this . state . sortOrder )
2018-11-19 11:53:44 +08:00
} ) ;
} ) . catch ( ( error ) => {
2019-12-05 15:45:16 +08:00
this . setState ( {
loading : false ,
errorMsg : Utils . getErrorMsg ( error , true ) // true: show login tip if 403
} ) ;
2018-11-19 11:53:44 +08:00
} ) ;
}
2018-12-27 10:24:34 +08:00
onRemoveLink = ( item ) => {
2019-01-03 11:56:32 +08:00
seafileAPI . deleteShareLink ( item . token ) . then ( ( ) => {
2018-12-27 10:24:34 +08:00
let items = this . state . items . filter ( uploadItem => {
return uploadItem . token !== item . token ;
} ) ;
this . setState ( { items : items } ) ;
2019-08-20 12:00:58 +08:00
let message = gettext ( 'Successfully deleted 1 item.' ) ;
2019-07-16 10:01:09 +08:00
toaster . success ( message ) ;
2018-12-27 10:24:34 +08:00
} ) . catch ( ( error ) => {
2019-07-16 10:01:09 +08:00
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
2018-12-27 10:24:34 +08:00
} ) ;
}
2018-11-19 11:53:44 +08:00
render ( ) {
return (
2019-01-10 15:09:47 +08:00
< div className = "main-panel-center" >
2018-11-26 17:53:18 +08:00
< div className = "cur-view-container" >
2019-02-20 13:11:13 +08:00
< div className = "cur-view-path share-upload-nav" >
2018-11-26 17:53:18 +08:00
< ul className = "nav" >
< li className = "nav-item" >
< Link to = { ` ${ siteRoot } share-admin-share-links/ ` } className = "nav-link active" > { gettext ( 'Share Links' ) } < / L i n k >
< / l i >
2018-12-27 10:24:34 +08:00
{ canGenerateUploadLink && (
2018-11-26 17:53:18 +08:00
< li className = "nav-item" > < Link to = { ` ${ siteRoot } share-admin-upload-links/ ` } className = "nav-link" > { gettext ( 'Upload Links' ) } < / L i n k > < / l i >
2018-12-27 10:24:34 +08:00
) }
2018-11-26 17:53:18 +08:00
< / u l >
< / d i v >
< div className = "cur-view-content" >
2018-12-27 10:24:34 +08:00
< Content
2019-01-02 17:49:30 +08:00
loading = { this . state . loading }
2018-12-27 10:24:34 +08:00
errorMsg = { this . state . errorMsg }
items = { this . state . items }
2019-01-02 17:49:30 +08:00
sortBy = { this . state . sortBy }
sortOrder = { this . state . sortOrder }
sortItems = { this . sortItems }
2018-12-27 10:24:34 +08:00
onRemoveLink = { this . onRemoveLink }
/ >
2018-11-26 14:00:32 +08:00
< / d i v >
2018-11-19 11:53:44 +08:00
< / d i v >
2018-11-26 17:53:18 +08:00
< / d i v >
2018-11-19 11:53:44 +08:00
) ;
}
}
export default ShareAdminShareLinks ;