2014-01-30 09:41:04 +00:00
/ *
* Copyright ( c ) 2014
*
* This file is licensed under the Affero General Public License version 3
* or later .
*
* See the COPYING - README file .
*
* /
2014-05-08 20:06:30 +00:00
( function ( ) {
2015-08-27 11:22:58 +00:00
2014-04-04 16:46:08 +00:00
/ * *
2014-06-23 21:56:10 +00:00
* @ class OCA . Files . FileList
* @ classdesc
*
2014-05-08 20:06:30 +00:00
* The FileList class manages a file list view .
* A file list view consists of a controls bar and
* a file list table .
2014-06-23 21:56:10 +00:00
*
* @ param $el container element with existing markup for the # controls
* and a table
2015-07-13 15:38:13 +00:00
* @ param { Object } [ options ] map of options , see other parameters
* @ param { Object } [ options . scrollContainer ] scrollable container , defaults to $ ( window )
* @ param { Object } [ options . dragOptions ] drag options , disabled by default
* @ param { Object } [ options . folderDropOptions ] folder drop options , disabled by default
* @ param { boolean } [ options . detailsViewEnabled = true ] whether to enable details view
2015-12-16 16:35:53 +00:00
* @ param { boolean } [ options . enableUpload = false ] whether to enable uploader
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . Client } [ options . filesClient ] files client to use
2014-04-04 16:46:08 +00:00
* /
2014-05-12 17:54:20 +00:00
var FileList = function ( $el , options ) {
this . initialize ( $el , options ) ;
2014-05-08 20:06:30 +00:00
} ;
2014-06-23 21:56:10 +00:00
/ * *
* @ memberof OCA . Files
* /
2014-05-08 20:06:30 +00:00
FileList . prototype = {
2014-07-21 21:07:52 +00:00
SORT _INDICATOR _ASC _CLASS : 'icon-triangle-n' ,
SORT _INDICATOR _DESC _CLASS : 'icon-triangle-s' ,
2014-05-08 20:06:30 +00:00
2014-05-12 17:54:20 +00:00
id : 'files' ,
2014-05-08 20:06:30 +00:00
appName : t ( 'files' , 'Files' ) ,
isEmpty : true ,
useUndo : true ,
/ * *
* Top - level container with controls and file list
* /
$el : null ,
/ * *
* Files table
* /
$table : null ,
/ * *
* List of rows ( table tbody )
* /
$fileList : null ,
2019-08-19 09:40:42 +00:00
$header : null ,
headers : [ ] ,
$footer : null ,
footers : [ ] ,
2014-06-23 21:56:10 +00:00
/ * *
* @ type OCA . Files . BreadCrumb
* /
2014-05-08 20:06:30 +00:00
breadcrumb : null ,
/ * *
2014-06-23 21:56:10 +00:00
* @ type OCA . Files . FileSummary
2014-05-08 20:06:30 +00:00
* /
fileSummary : null ,
2014-06-23 21:56:10 +00:00
2015-07-15 10:06:13 +00:00
/ * *
* @ type OCA . Files . DetailsView
* /
_detailsView : null ,
2015-07-13 15:38:13 +00:00
/ * *
* Files client instance
*
* @ type OC . Files . Client
* /
filesClient : null ,
2014-06-23 21:56:10 +00:00
/ * *
* Whether the file list was initialized already .
* @ type boolean
* /
2014-05-08 20:06:30 +00:00
initialized : false ,
2017-05-04 11:23:04 +00:00
/ * *
* Wheater the file list was already shown once
* @ type boolean
* /
shown : false ,
2014-06-23 21:56:10 +00:00
/ * *
* Number of files per page
2018-07-20 12:19:00 +00:00
* Always show a minimum of 1
2014-06-23 21:56:10 +00:00
*
* @ return { int } page size
* /
2014-10-11 13:10:54 +00:00
pageSize : function ( ) {
2018-11-18 09:34:35 +00:00
var isGridView = this . $showGridView . is ( ':checked' ) ;
var columns = 1 ;
var rows = Math . ceil ( this . $container . height ( ) / 50 ) ;
if ( isGridView ) {
columns = Math . ceil ( this . $container . width ( ) / 160 ) ;
rows = Math . ceil ( this . $container . height ( ) / 160 ) ;
}
return Math . max ( columns * rows , columns ) ;
2014-10-11 13:10:54 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Array of files in the current folder .
* The entries are of file data .
2014-06-23 21:56:10 +00:00
*
2015-07-13 15:38:13 +00:00
* @ type Array . < OC . Files . FileInfo >
2014-05-08 20:06:30 +00:00
* /
files : [ ] ,
2015-07-13 15:38:13 +00:00
/ * *
* Current directory entry
*
* @ type OC . Files . FileInfo
* /
dirInfo : null ,
2014-05-08 20:06:30 +00:00
/ * *
* File actions handler , defaults to OCA . Files . FileActions
2014-06-23 21:56:10 +00:00
* @ type OCA . Files . FileActions
2014-05-08 20:06:30 +00:00
* /
fileActions : null ,
2018-05-28 01:49:01 +00:00
/ * *
* File selection menu , defaults to OCA . Files . FileSelectionMenu
* @ type OCA . Files . FileSelectionMenu
* /
2018-06-02 15:15:19 +00:00
fileMultiSelectMenu : null ,
2014-12-11 16:36:14 +00:00
/ * *
* Whether selection is allowed , checkboxes and selection overlay will
* be rendered
* /
_allowSelection : true ,
2014-05-08 20:06:30 +00:00
/ * *
* Map of file id to file data
2014-06-23 21:56:10 +00:00
* @ type Object . < int , Object >
2014-05-08 20:06:30 +00:00
* /
_selectedFiles : { } ,
/ * *
* Summary of selected files .
2014-06-23 21:56:10 +00:00
* @ type OCA . Files . FileSummary
2014-05-08 20:06:30 +00:00
* /
_selectionSummary : null ,
2014-12-18 09:26:41 +00:00
/ * *
* If not empty , only files containing this string will be shown
* @ type String
* /
_filter : '' ,
2016-04-12 15:10:09 +00:00
/ * *
* @ type Backbone . Model
* /
2016-04-21 12:23:18 +00:00
_filesConfig : undefined ,
2016-04-12 15:10:09 +00:00
2014-05-08 20:06:30 +00:00
/ * *
* Sort attribute
2014-06-23 21:56:10 +00:00
* @ type String
2014-05-08 20:06:30 +00:00
* /
_sort : 'name' ,
/ * *
* Sort direction : 'asc' or 'desc'
2014-06-23 21:56:10 +00:00
* @ type String
2014-05-08 20:06:30 +00:00
* /
_sortDirection : 'asc' ,
/ * *
* Sort comparator function for the current sort
2014-06-23 21:56:10 +00:00
* @ type Function
2014-05-08 20:06:30 +00:00
* /
_sortComparator : null ,
2014-08-15 14:52:41 +00:00
/ * *
* Whether to do a client side sort .
* When false , clicking on a table header will call reload ( ) .
* When true , clicking on a table header will simply resort the list .
* /
2015-07-13 15:38:13 +00:00
_clientSideSort : true ,
2014-08-15 14:52:41 +00:00
2016-07-22 12:19:04 +00:00
/ * *
* Whether or not users can change the sort attribute or direction
* /
_allowSorting : true ,
2014-05-08 20:06:30 +00:00
/ * *
* Current directory
2014-06-23 21:56:10 +00:00
* @ type String
2014-05-08 20:06:30 +00:00
* /
_currentDirectory : null ,
2014-05-12 17:54:20 +00:00
_dragOptions : null ,
_folderDropOptions : null ,
2015-12-16 16:35:53 +00:00
/ * *
* @ type OC . Uploader
* /
_uploader : null ,
2014-05-08 20:06:30 +00:00
/ * *
* Initialize the file list and its components
2014-05-12 17:54:20 +00:00
*
* @ param $el container element with existing markup for the # controls
* and a table
* @ param options map of options , see other parameters
2014-09-04 10:20:11 +00:00
* @ param options . scrollContainer scrollable container , defaults to $ ( window )
* @ param options . dragOptions drag options , disabled by default
* @ param options . folderDropOptions folder drop options , disabled by default
* @ param options . scrollTo name of file to scroll to after the first load
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . Client } [ options . filesClient ] files API client
2016-08-19 14:44:58 +00:00
* @ param { OC . Backbone . Model } [ options . filesConfig ] files app configuration
2014-06-23 21:56:10 +00:00
* @ private
2014-05-08 20:06:30 +00:00
* /
2014-05-12 17:54:20 +00:00
initialize : function ( $el , options ) {
2014-05-08 20:06:30 +00:00
var self = this ;
2014-05-12 17:54:20 +00:00
options = options || { } ;
2014-05-08 20:06:30 +00:00
if ( this . initialized ) {
return ;
}
2014-04-04 16:46:08 +00:00
Fix opening a section again in the Files app
When a section is open in the Files app a "show" event is triggered.
File list objects handle that event by reloading themselves, but only
if the file list was shown at least once. However, the file list objects
of plugins are created when the "show" event is triggered for the first
time for their section; as the file list objects register their handler
for the "show" event when they are created they never handle the first
triggered "show" event, as the handler is set while that event is being
already handled. Therefore, from the point of view of the handler, the
second time that a "show" event was triggered it was seen as if the file
list was shown for the first time, and thus it was not reloaded. Now the
"shown" property is explicitly set for those file lists that are created
while handling a "show" event, which causes them to be reloaded as
expected when opening their section again.
Note that it is not possible to just reload the file list whenever it is
shown; the file list is reloaded also when the directory changes, and
this can happen when the web page is initially loaded and the URL is
parsed. In that case, if file lists were reloaded when shown for the
first time then it could be reloaded twice, one with the default
parameters due to the "show" event and another one with the proper
parameters once the URL was parsed, and the files that appeard in the
list would depend on which response from the server was received the
last.
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2018-10-19 18:32:15 +00:00
if ( options . shown ) {
this . shown = options . shown ;
}
2016-04-12 15:10:09 +00:00
if ( options . config ) {
this . _filesConfig = options . config ;
2016-04-21 12:23:18 +00:00
} else if ( ! _ . isUndefined ( OCA . Files ) && ! _ . isUndefined ( OCA . Files . App ) ) {
2016-04-12 15:10:09 +00:00
this . _filesConfig = OCA . Files . App . getFilesConfig ( ) ;
2016-08-22 07:45:21 +00:00
} else {
this . _filesConfig = new OC . Backbone . Model ( {
'showhidden' : false
} ) ;
2016-04-12 15:10:09 +00:00
}
2016-04-21 12:23:18 +00:00
2014-05-12 17:54:20 +00:00
if ( options . dragOptions ) {
this . _dragOptions = options . dragOptions ;
}
if ( options . folderDropOptions ) {
this . _folderDropOptions = options . folderDropOptions ;
}
2015-07-13 15:38:13 +00:00
if ( options . filesClient ) {
this . filesClient = options . filesClient ;
} else {
// default client if not specified
this . filesClient = OC . Files . getClient ( ) ;
}
2014-05-12 17:54:20 +00:00
2014-05-08 20:06:30 +00:00
this . $el = $el ;
2014-12-01 15:17:28 +00:00
if ( options . id ) {
this . id = options . id ;
}
2014-05-12 17:54:20 +00:00
this . $container = options . scrollContainer || $ ( window ) ;
2014-05-08 20:06:30 +00:00
this . $table = $el . find ( 'table:first' ) ;
this . $fileList = $el . find ( '#fileList' ) ;
2019-08-19 09:40:42 +00:00
this . $header = $el . find ( '#filelist-header' ) ;
this . $footer = $el . find ( '#filelist-footer' ) ;
2015-09-02 08:41:08 +00:00
2016-08-17 15:34:15 +00:00
if ( ! _ . isUndefined ( this . _filesConfig ) ) {
this . _filesConfig . on ( 'change:showhidden' , function ( ) {
var showHidden = this . get ( 'showhidden' ) ;
self . $el . toggleClass ( 'hide-hidden-files' , ! showHidden ) ;
2016-08-19 14:44:58 +00:00
self . updateSelectionSummary ( ) ;
2016-08-17 15:34:15 +00:00
if ( ! showHidden ) {
// hiding files could make the page too small, need to try rendering next page
self . _onScroll ( ) ;
}
} ) ;
this . $el . toggleClass ( 'hide-hidden-files' , ! this . _filesConfig . get ( 'showhidden' ) ) ;
}
2015-09-02 08:41:08 +00:00
if ( _ . isUndefined ( options . detailsViewEnabled ) || options . detailsViewEnabled ) {
this . _detailsView = new OCA . Files . DetailsView ( ) ;
2018-06-16 07:37:55 +00:00
this . _detailsView . $el . addClass ( 'disappear' ) ;
2015-09-02 08:41:08 +00:00
}
2014-05-20 14:01:34 +00:00
this . _initFileActions ( options . fileActions ) ;
2015-09-02 08:41:08 +00:00
if ( this . _detailsView ) {
this . _detailsView . addDetailView ( new OCA . Files . MainFileInfoDetailView ( { fileList : this , fileActions : this . fileActions } ) ) ;
}
2014-05-08 20:06:30 +00:00
this . files = [ ] ;
this . _selectedFiles = { } ;
2016-08-19 14:44:58 +00:00
this . _selectionSummary = new OCA . Files . FileSummary ( undefined , { config : this . _filesConfig } ) ;
2015-07-13 15:38:13 +00:00
// dummy root dir info
this . dirInfo = new OC . Files . FileInfo ( { } ) ;
2014-04-03 18:57:06 +00:00
2014-05-08 20:06:30 +00:00
this . fileSummary = this . _createSummary ( ) ;
2014-04-03 18:57:06 +00:00
2018-06-02 15:15:19 +00:00
if ( options . multiSelectMenu ) {
2019-01-13 16:15:43 +00:00
this . multiSelectMenuItems = options . multiSelectMenu ;
for ( var i = 0 ; i < this . multiSelectMenuItems . length ; i ++ ) {
if ( _ . isFunction ( this . multiSelectMenuItems [ i ] ) ) {
this . multiSelectMenuItems [ i ] = this . multiSelectMenuItems [ i ] ( this ) ;
}
}
this . fileMultiSelectMenu = new OCA . Files . FileMultiSelectMenu ( this . multiSelectMenuItems ) ;
2018-06-06 18:01:25 +00:00
this . fileMultiSelectMenu . render ( ) ;
this . $el . find ( '.selectedActions' ) . append ( this . fileMultiSelectMenu . $el ) ;
2018-06-02 15:15:19 +00:00
}
2018-05-28 01:49:01 +00:00
2016-04-12 08:19:52 +00:00
if ( options . sorting ) {
2016-04-12 09:51:50 +00:00
this . setSort ( options . sorting . mode , options . sorting . direction , false , false ) ;
2016-04-12 08:19:52 +00:00
} else {
2016-04-12 09:51:50 +00:00
this . setSort ( 'name' , 'asc' , false , false ) ;
2016-04-12 08:19:52 +00:00
}
2014-04-04 12:34:07 +00:00
2014-05-12 17:54:20 +00:00
var breadcrumbOptions = {
2014-05-08 20:06:30 +00:00
onClick : _ . bind ( this . _onClickBreadCrumb , this ) ,
2014-05-12 17:54:20 +00:00
getCrumbUrl : function ( part ) {
2014-05-08 20:06:30 +00:00
return self . linkTo ( part . dir ) ;
}
2014-05-12 17:54:20 +00:00
} ;
// if dropping on folders is allowed, then also allow on breadcrumbs
if ( this . _folderDropOptions ) {
breadcrumbOptions . onDrop = _ . bind ( this . _onDropOnBreadCrumb , this ) ;
2015-11-04 10:44:47 +00:00
breadcrumbOptions . onOver = function ( ) {
self . $el . find ( 'td.filename.ui-droppable' ) . droppable ( 'disable' ) ;
2018-10-19 09:23:33 +00:00
} ;
2015-11-04 10:44:47 +00:00
breadcrumbOptions . onOut = function ( ) {
self . $el . find ( 'td.filename.ui-droppable' ) . droppable ( 'enable' ) ;
2018-10-19 09:23:33 +00:00
} ;
2014-05-12 17:54:20 +00:00
}
this . breadcrumb = new OCA . Files . BreadCrumb ( breadcrumbOptions ) ;
2013-10-28 19:22:06 +00:00
2015-10-13 09:36:24 +00:00
var $controls = this . $el . find ( '#controls' ) ;
if ( $controls . length > 0 ) {
$controls . prepend ( this . breadcrumb . $el ) ;
this . $table . addClass ( 'has-controls' ) ;
}
2013-10-28 19:22:06 +00:00
2015-08-27 11:22:58 +00:00
this . _renderNewButton ( ) ;
2014-05-08 20:06:30 +00:00
this . $el . find ( 'thead th .columntitle' ) . click ( _ . bind ( this . _onClickHeader , this ) ) ;
2014-02-11 15:52:56 +00:00
2018-11-06 08:06:24 +00:00
// Toggle for grid view, only register once
this . $showGridView = $ ( 'input#showgridview:not(.registered)' ) ;
2018-10-04 13:14:15 +00:00
this . $showGridView . on ( 'change' , _ . bind ( this . _onGridviewChange , this ) ) ;
2018-11-06 08:06:24 +00:00
this . $showGridView . addClass ( 'registered' ) ;
2018-10-23 06:27:27 +00:00
$ ( '#view-toggle' ) . tooltip ( { placement : 'bottom' , trigger : 'hover' } ) ;
2018-10-03 08:59:31 +00:00
2017-11-04 08:45:29 +00:00
this . _onResize = _ . debounce ( _ . bind ( this . _onResize , this ) , 250 ) ;
2015-10-02 16:02:55 +00:00
$ ( '#app-content' ) . on ( 'appresized' , this . _onResize ) ;
2014-05-23 17:02:50 +00:00
$ ( window ) . resize ( this . _onResize ) ;
this . $el . on ( 'show' , this . _onResize ) ;
2014-04-03 18:57:06 +00:00
2015-01-05 12:11:50 +00:00
this . updateSearch ( ) ;
2016-03-23 11:31:58 +00:00
this . $fileList . on ( 'click' , 'td.filename>a.name, td.filesize, td.date' , _ . bind ( this . _onClickFile , this ) ) ;
2018-12-07 17:33:05 +00:00
this . $fileList . on ( "droppedOnFavorites" , function ( event , file ) {
self . fileActions . triggerAction ( 'Favorite' , self . getModelForFile ( file ) , self ) ;
} ) ;
2019-01-31 18:09:15 +00:00
this . $fileList . on ( 'droppedOnTrash' , function ( event , filename , directory ) {
2019-01-31 18:00:06 +00:00
self . do _delete ( filename , directory ) ;
2018-08-28 16:21:46 +00:00
} ) ;
2017-09-28 23:36:10 +00:00
this . $fileList . on ( 'change' , 'td.selection>.selectCheckBox' , _ . bind ( this . _onClickFileCheckbox , this ) ) ;
2019-01-13 16:15:43 +00:00
this . $fileList . on ( 'mouseover' , 'td.selection' , _ . bind ( this . _onMouseOverCheckbox , this ) ) ;
2017-04-20 12:59:08 +00:00
this . $el . on ( 'show' , _ . bind ( this . _onShow , this ) ) ;
2014-05-08 20:06:30 +00:00
this . $el . on ( 'urlChanged' , _ . bind ( this . _onUrlChanged , this ) ) ;
2014-05-12 17:54:20 +00:00
this . $el . find ( '.select-all' ) . click ( _ . bind ( this . _onClickSelectAll , this ) ) ;
2018-05-28 01:49:01 +00:00
this . $el . find ( '.actions-selected' ) . click ( function ( ) {
2018-06-02 15:15:19 +00:00
self . fileMultiSelectMenu . show ( self ) ;
2018-05-28 01:49:01 +00:00
return false ;
} ) ;
2014-05-08 20:06:30 +00:00
2014-05-12 17:54:20 +00:00
this . $container . on ( 'scroll' , _ . bind ( this . _onScroll , this ) ) ;
2014-09-04 10:20:11 +00:00
if ( options . scrollTo ) {
this . $fileList . one ( 'updated' , function ( ) {
self . scrollTo ( options . scrollTo ) ;
} ) ;
}
2014-12-01 15:17:28 +00:00
2018-11-13 17:30:32 +00:00
this . _operationProgressBar = new OCA . Files . OperationProgressBar ( ) ;
this . _operationProgressBar . render ( ) ;
this . $el . find ( '#uploadprogresswrapper' ) . replaceWith ( this . _operationProgressBar . $el ) ;
2015-12-16 16:35:53 +00:00
if ( options . enableUpload ) {
// TODO: auto-create this element
var $uploadEl = this . $el . find ( '#file_upload_start' ) ;
if ( $uploadEl . exists ( ) ) {
this . _uploader = new OC . Uploader ( $uploadEl , {
2018-11-13 17:30:32 +00:00
progressBar : this . _operationProgressBar ,
2015-12-16 16:35:53 +00:00
fileList : this ,
filesClient : this . filesClient ,
2016-10-07 14:27:54 +00:00
dropZone : $ ( '#content' ) ,
maxChunkSize : options . maxChunkSize
2015-12-16 16:35:53 +00:00
} ) ;
this . setupUploadEvents ( this . _uploader ) ;
}
}
2018-05-28 01:49:01 +00:00
2014-12-01 15:17:28 +00:00
OC . Plugins . attach ( 'OCA.Files.FileList' , this ) ;
2019-08-19 09:40:42 +00:00
this . initHeadersAndFooters ( )
} ,
initHeadersAndFooters : function ( ) {
this . headers . sort ( function ( a , b ) {
return a . order - b . order ;
} )
this . footers . sort ( function ( a , b ) {
return a . order - b . order ;
} )
var uniqueIds = [ ] ;
var self = this ;
this . headers . forEach ( function ( header ) {
if ( header . id ) {
if ( uniqueIds . indexOf ( header . id ) !== - 1 ) {
return
}
uniqueIds . push ( header . id )
}
self . $header . append ( header . el )
setTimeout ( function ( ) {
header . render ( self )
} , 0 )
} )
uniqueIds = [ ] ;
this . footers . forEach ( function ( footer ) {
if ( footer . id ) {
if ( uniqueIds . indexOf ( footer . id ) !== - 1 ) {
return
}
uniqueIds . push ( footer . id )
}
self . $footer . append ( footer . el )
setTimeout ( function ( ) {
footer . render ( self )
} , 0 )
} )
2014-05-08 20:06:30 +00:00
} ,
2014-06-27 11:36:18 +00:00
/ * *
* Destroy / uninitialize this instance .
* /
destroy : function ( ) {
2015-08-27 11:22:58 +00:00
if ( this . _newFileMenu ) {
this . _newFileMenu . remove ( ) ;
}
2015-09-03 10:17:35 +00:00
if ( this . _newButton ) {
this . _newButton . remove ( ) ;
}
2015-09-25 10:23:28 +00:00
if ( this . _detailsView ) {
this . _detailsView . remove ( ) ;
}
2014-06-27 11:36:18 +00:00
// TODO: also unregister other event handlers
2014-07-09 10:26:33 +00:00
this . fileActions . off ( 'registerAction' , this . _onFileActionsUpdated ) ;
this . fileActions . off ( 'setDefault' , this . _onFileActionsUpdated ) ;
2014-12-01 15:17:28 +00:00
OC . Plugins . detach ( 'OCA.Files.FileList' , this ) ;
2015-10-02 16:02:55 +00:00
$ ( '#app-content' ) . off ( 'appresized' , this . _onResize ) ;
2014-06-27 11:36:18 +00:00
} ,
2019-01-13 16:15:43 +00:00
_selectionMode : 'single' ,
_getCurrentSelectionMode : function ( ) {
return this . _selectionMode ;
} ,
_onClickToggleSelectionMode : function ( ) {
this . _selectionMode = ( this . _selectionMode === 'range' ) ? 'single' : 'range' ;
if ( this . _selectionMode === 'single' ) {
this . _removeHalfSelection ( ) ;
}
} ,
2018-06-02 15:15:19 +00:00
multiSelectMenuClick : function ( ev , action ) {
2019-01-13 16:15:43 +00:00
var actionFunction = _ . find ( this . multiSelectMenuItems , function ( item ) { return item . name === action ; } ) . action ;
if ( actionFunction ) {
actionFunction ( ev ) ;
return ;
}
2018-06-02 15:15:19 +00:00
switch ( action ) {
case 'delete' :
this . _onClickDeleteSelected ( ev )
break ;
case 'download' :
this . _onClickDownloadSelected ( ev ) ;
break ;
2018-06-06 18:01:25 +00:00
case 'copyMove' :
2018-06-02 15:15:19 +00:00
this . _onClickCopyMoveSelected ( ev ) ;
break ;
case 'restore' :
this . _onClickRestoreSelected ( ev ) ;
break ;
}
} ,
2014-06-23 21:56:10 +00:00
/ * *
* Initializes the file actions , set up listeners .
*
* @ param { OCA . Files . FileActions } fileActions file actions
* /
2014-05-20 14:01:34 +00:00
_initFileActions : function ( fileActions ) {
2015-09-02 08:41:08 +00:00
var self = this ;
2014-05-20 14:01:34 +00:00
this . fileActions = fileActions ;
if ( ! this . fileActions ) {
this . fileActions = new OCA . Files . FileActions ( ) ;
this . fileActions . registerDefaultActions ( ) ;
}
2015-09-02 08:41:08 +00:00
if ( this . _detailsView ) {
this . fileActions . registerAction ( {
name : 'Details' ,
2015-09-22 12:52:52 +00:00
displayName : t ( 'files' , 'Details' ) ,
2015-09-02 08:41:08 +00:00
mime : 'all' ,
2015-09-28 09:19:49 +00:00
order : - 50 ,
2016-02-17 10:04:29 +00:00
iconClass : 'icon-details' ,
2017-11-02 14:01:17 +00:00
permissions : OC . PERMISSION _NONE ,
2015-09-02 08:41:08 +00:00
actionHandler : function ( fileName , context ) {
self . _updateDetailsView ( fileName ) ;
}
} ) ;
}
2014-06-27 11:36:18 +00:00
this . _onFileActionsUpdated = _ . debounce ( _ . bind ( this . _onFileActionsUpdated , this ) , 100 ) ;
2014-07-09 10:26:33 +00:00
this . fileActions . on ( 'registerAction' , this . _onFileActionsUpdated ) ;
this . fileActions . on ( 'setDefault' , this . _onFileActionsUpdated ) ;
2014-05-20 14:01:34 +00:00
} ,
2015-08-12 15:30:20 +00:00
/ * *
* Returns a unique model for the given file name .
*
* @ param { string | object } fileName file name or jquery row
* @ return { OCA . Files . FileInfoModel } file info model
* /
getModelForFile : function ( fileName ) {
2015-09-01 17:29:55 +00:00
var self = this ;
2015-08-12 15:30:20 +00:00
var $tr ;
// jQuery object ?
if ( fileName . is ) {
$tr = fileName ;
fileName = $tr . attr ( 'data-file' ) ;
} else {
$tr = this . findFileEl ( fileName ) ;
}
if ( ! $tr || ! $tr . length ) {
return null ;
}
// if requesting the selected model, return it
if ( this . _currentFileModel && this . _currentFileModel . get ( 'name' ) === fileName ) {
return this . _currentFileModel ;
}
// TODO: note, this is a temporary model required for synchronising
// state between different views.
// In the future the FileList should work with Backbone.Collection
// and contain existing models that can be used.
// This method would in the future simply retrieve the matching model from the collection.
2016-11-21 14:03:45 +00:00
var model = new OCA . Files . FileInfoModel ( this . elementToFile ( $tr ) , {
filesClient : this . filesClient
} ) ;
2015-09-11 13:24:33 +00:00
if ( ! model . get ( 'path' ) ) {
2015-08-12 15:30:20 +00:00
model . set ( 'path' , this . getCurrentDirectory ( ) , { silent : true } ) ;
}
2015-09-01 17:29:55 +00:00
model . on ( 'change' , function ( model ) {
// re-render row
var highlightState = $tr . hasClass ( 'highlighted' ) ;
$tr = self . updateRow (
$tr ,
2015-07-13 15:38:13 +00:00
model . toJSON ( ) ,
2015-09-01 17:29:55 +00:00
{ updateSummary : true , silent : false , animate : true }
) ;
2016-04-13 09:20:26 +00:00
// restore selection state
var selected = ! ! self . _selectedFiles [ $tr . data ( 'id' ) ] ;
self . _selectFileEl ( $tr , selected ) ;
2015-09-01 17:29:55 +00:00
$tr . toggleClass ( 'highlighted' , highlightState ) ;
} ) ;
model . on ( 'busy' , function ( model , state ) {
self . showFileBusyState ( $tr , state ) ;
} ) ;
2015-08-12 15:30:20 +00:00
return model ;
} ,
2015-09-14 15:20:51 +00:00
/ * *
* Displays the details view for the given file and
* selects the given tab
*
2016-10-10 15:35:11 +00:00
* @ param { string | OCA . Files . FileInfoModel } fileName file name or FileInfoModel for which to show details
2015-09-14 15:20:51 +00:00
* @ param { string } [ tabId ] optional tab id to select
* /
showDetailsView : function ( fileName , tabId ) {
this . _updateDetailsView ( fileName ) ;
if ( tabId ) {
this . _detailsView . selectTab ( tabId ) ;
}
2015-09-22 13:28:48 +00:00
OC . Apps . showAppSidebar ( this . _detailsView . $el ) ;
2015-09-14 15:20:51 +00:00
} ,
2015-07-15 10:06:13 +00:00
/ * *
* Update the details view to display the given file
*
2016-10-10 15:35:11 +00:00
* @ param { string | OCA . Files . FileInfoModel } fileName file name from the current list or a FileInfoModel object
2015-10-21 11:43:39 +00:00
* @ param { boolean } [ show = true ] whether to open the sidebar if it was closed
2015-07-15 10:06:13 +00:00
* /
2015-10-21 11:43:39 +00:00
_updateDetailsView : function ( fileName , show ) {
2015-07-16 10:28:19 +00:00
if ( ! this . _detailsView ) {
return ;
}
2015-10-21 11:43:39 +00:00
// show defaults to true
show = _ . isUndefined ( show ) || ! ! show ;
2015-07-15 15:35:35 +00:00
var oldFileInfo = this . _detailsView . getFileInfo ( ) ;
if ( oldFileInfo ) {
// TODO: use more efficient way, maybe track the highlight
2015-08-12 15:30:20 +00:00
this . $fileList . children ( ) . filterAttr ( 'data-id' , '' + oldFileInfo . get ( 'id' ) ) . removeClass ( 'highlighted' ) ;
oldFileInfo . off ( 'change' , this . _onSelectedModelChanged , this ) ;
2015-07-15 15:35:35 +00:00
}
2015-08-12 15:30:20 +00:00
if ( ! fileName ) {
2015-07-15 15:05:25 +00:00
this . _detailsView . setFileInfo ( null ) ;
2015-09-01 17:29:55 +00:00
if ( this . _currentFileModel ) {
this . _currentFileModel . off ( ) ;
}
2015-08-12 15:30:20 +00:00
this . _currentFileModel = null ;
2015-09-25 10:23:28 +00:00
OC . Apps . hideAppSidebar ( this . _detailsView . $el ) ;
2015-07-15 14:09:00 +00:00
return ;
}
2015-10-21 11:43:39 +00:00
if ( show && this . _detailsView . $el . hasClass ( 'disappear' ) ) {
2015-09-25 10:23:28 +00:00
OC . Apps . showAppSidebar ( this . _detailsView . $el ) ;
}
2016-10-10 15:35:11 +00:00
if ( fileName instanceof OCA . Files . FileInfoModel ) {
var model = fileName ;
2016-10-04 17:08:08 +00:00
} else {
var $tr = this . findFileEl ( fileName ) ;
var model = this . getModelForFile ( $tr ) ;
$tr . addClass ( 'highlighted' ) ;
}
2015-08-12 15:30:20 +00:00
this . _currentFileModel = model ;
Fix rendering of the sidebar in Files app
When a view is rendered it should not be concerned with where it is
going to be placed in the document; in general this should be a
responsibility of the object using the view.
Moreover, when the details view is rendered it should simply prepare a
skeleton that includes the root elements provided by the plugins; those
elements will be updated by the plugins as needed when a file or a tab
is selected.
Finally, the details view should not be explicitly rendered. The
rendering removes the previous elements, but that is needed only when
the details view is in a dirty state, that is, when new plugins were
added since the last time that it was rendered. However, that dirty
state is internally handled, and the view is automatically rendered
again if needed when a file info is set.
Due to all that the details view is no longer explicitly rendered when
updating it with a different file. Also, as each file list has its own
details view, and each details view has its own element, but there can
be only one details view/sidebar element in the document, when the file
list updates the details view it also replaces the current one in the
document with its own details view if needed (that is, if it is not the
current one already).
Besides that, when the element of a details view is replaced with the
element of a different details view the old one should be detached from
the document, but never removed. Otherwise the event handlers would not
work when that element is attached again later (when changing to a
different section in the Files app and then going back to the previous
one).
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2018-11-22 04:58:25 +00:00
this . _replaceDetailsViewElementIfNeeded ( ) ;
2015-08-12 15:30:20 +00:00
this . _detailsView . setFileInfo ( model ) ;
2015-07-16 10:21:40 +00:00
this . _detailsView . $el . scrollTop ( 0 ) ;
2015-07-15 10:06:13 +00:00
} ,
Fix rendering of the sidebar in Files app
When a view is rendered it should not be concerned with where it is
going to be placed in the document; in general this should be a
responsibility of the object using the view.
Moreover, when the details view is rendered it should simply prepare a
skeleton that includes the root elements provided by the plugins; those
elements will be updated by the plugins as needed when a file or a tab
is selected.
Finally, the details view should not be explicitly rendered. The
rendering removes the previous elements, but that is needed only when
the details view is in a dirty state, that is, when new plugins were
added since the last time that it was rendered. However, that dirty
state is internally handled, and the view is automatically rendered
again if needed when a file info is set.
Due to all that the details view is no longer explicitly rendered when
updating it with a different file. Also, as each file list has its own
details view, and each details view has its own element, but there can
be only one details view/sidebar element in the document, when the file
list updates the details view it also replaces the current one in the
document with its own details view if needed (that is, if it is not the
current one already).
Besides that, when the element of a details view is replaced with the
element of a different details view the old one should be detached from
the document, but never removed. Otherwise the event handlers would not
work when that element is attached again later (when changing to a
different section in the Files app and then going back to the previous
one).
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2018-11-22 04:58:25 +00:00
/ * *
* Replaces the current details view element with the details view
* element of this file list .
*
* Each file list has its own DetailsView object , and each one has its
* own root element , but there can be just one details view / sidebar
* element in the document . This helper method replaces the current
* details view / sidebar element in the document with the element from
* the DetailsView object of this file list .
* /
_replaceDetailsViewElementIfNeeded : function ( ) {
var $appSidebar = $ ( '#app-sidebar' ) ;
if ( $appSidebar . length === 0 ) {
this . _detailsView . $el . insertAfter ( $ ( '#app-content' ) ) ;
} else if ( $appSidebar [ 0 ] !== this . _detailsView . el ) {
// "replaceWith()" can not be used here, as it removes the old
// element instead of just detaching it.
this . _detailsView . $el . insertBefore ( $appSidebar ) ;
$appSidebar . detach ( ) ;
}
} ,
2014-05-23 17:02:50 +00:00
/ * *
* Event handler for when the window size changed
* /
_onResize : function ( ) {
var containerWidth = this . $el . width ( ) ;
var actionsWidth = 0 ;
$ . each ( this . $el . find ( '#controls .actions' ) , function ( index , action ) {
actionsWidth += $ ( action ) . outerWidth ( ) ;
} ) ;
2017-11-04 08:45:29 +00:00
this . breadcrumb . _resize ( ) ;
2014-05-23 17:02:50 +00:00
} ,
2018-10-03 08:59:31 +00:00
/ * *
2018-10-04 13:14:15 +00:00
* Toggle showing gridview by default or not
*
* @ returns { undefined }
2018-10-03 08:59:31 +00:00
* /
2018-10-04 13:14:15 +00:00
_onGridviewChange : function ( ) {
var show = this . $showGridView . is ( ':checked' ) ;
2018-10-23 06:43:02 +00:00
// only save state if user is logged in
if ( OC . currentUser ) {
$ . post ( OC . generateUrl ( '/apps/files/api/v1/showgridview' ) , {
show : show
} ) ;
}
2018-10-04 13:14:15 +00:00
this . $showGridView . next ( '#view-toggle' )
. removeClass ( 'icon-toggle-filelist icon-toggle-pictures' )
. addClass ( show ? 'icon-toggle-filelist' : 'icon-toggle-pictures' )
2018-12-10 21:52:24 +00:00
2018-10-04 13:14:15 +00:00
$ ( '.list-container' ) . toggleClass ( 'view-grid' , show ) ;
2019-08-19 19:18:05 +00:00
if ( show ) {
// If switching into grid view from list view, too few files might be displayed
// Try rendering the next page
this . _onScroll ( ) ;
}
2018-10-03 08:59:31 +00:00
} ,
2017-04-20 12:59:08 +00:00
/ * *
* Event handler when leaving previously hidden state
* /
_onShow : function ( e ) {
2017-05-04 11:23:04 +00:00
if ( this . shown ) {
2018-11-20 13:44:20 +00:00
if ( e . itemId === this . id ) {
this . _setCurrentDir ( '/' , false ) ;
}
// Only reload if we don't navigate to a different directory
if ( typeof e . dir === 'undefined' || e . dir === this . getCurrentDirectory ( ) ) {
this . reload ( ) ;
}
2017-05-04 11:23:04 +00:00
}
this . shown = true ;
2017-04-20 12:59:08 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Event handler for when the URL changed
* /
_onUrlChanged : function ( e ) {
2016-07-01 09:10:37 +00:00
if ( e && _ . isString ( e . dir ) ) {
2017-03-01 11:57:48 +00:00
var currentDir = this . getCurrentDirectory ( ) ;
// this._currentDirectory is NULL when fileList is first initialised
if ( ( this . _currentDirectory || this . $el . find ( '#dir' ) . val ( ) ) && currentDir === e . dir ) {
return ;
}
2014-05-08 20:06:30 +00:00
this . changeDirectory ( e . dir , false , true ) ;
2013-10-28 19:22:06 +00:00
}
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Selected / deselects the given file element and updated
* the internal selection cache .
*
2015-09-25 10:23:28 +00:00
* @ param { Object } $tr single file row element
* @ param { bool } state true to select , false to deselect
2014-05-08 20:06:30 +00:00
* /
2019-01-13 16:15:43 +00:00
_selectFileEl : function ( $tr , state ) {
2017-09-28 23:36:10 +00:00
var $checkbox = $tr . find ( 'td.selection>.selectCheckBox' ) ;
2014-05-08 20:06:30 +00:00
var oldData = ! ! this . _selectedFiles [ $tr . data ( 'id' ) ] ;
var data ;
$checkbox . prop ( 'checked' , state ) ;
$tr . toggleClass ( 'selected' , state ) ;
// already selected ?
if ( state === oldData ) {
return ;
}
data = this . elementToFile ( $tr ) ;
if ( state ) {
this . _selectedFiles [ $tr . data ( 'id' ) ] = data ;
this . _selectionSummary . add ( data ) ;
}
else {
delete this . _selectedFiles [ $tr . data ( 'id' ) ] ;
this . _selectionSummary . remove ( data ) ;
}
2016-02-15 13:58:44 +00:00
if ( this . _detailsView && ! this . _detailsView . $el . hasClass ( 'disappear' ) ) {
// hide sidebar
this . _updateDetailsView ( null ) ;
2015-07-15 15:05:25 +00:00
}
2014-05-12 17:54:20 +00:00
this . $el . find ( '.select-all' ) . prop ( 'checked' , this . _selectionSummary . getTotal ( ) === this . files . length ) ;
2014-05-08 20:06:30 +00:00
} ,
2019-01-13 16:15:43 +00:00
_selectRange : function ( $tr ) {
var checked = $tr . hasClass ( 'selected' ) ;
var $lastTr = $ ( this . _lastChecked ) ;
var lastIndex = $lastTr . index ( ) ;
var currentIndex = $tr . index ( ) ;
var $rows = this . $fileList . children ( 'tr' ) ;
// last clicked checkbox below current one ?
if ( lastIndex > currentIndex ) {
var aux = lastIndex ;
lastIndex = currentIndex ;
currentIndex = aux ;
}
// auto-select everything in-between
for ( var i = lastIndex ; i <= currentIndex ; i ++ ) {
this . _selectFileEl ( $rows . eq ( i ) , ! checked ) ;
}
this . _removeHalfSelection ( ) ;
this . _selectionMode = 'single' ;
} ,
_selectSingle : function ( $tr ) {
var state = ! $tr . hasClass ( 'selected' ) ;
this . _selectFileEl ( $tr , state ) ;
} ,
_onMouseOverCheckbox : function ( e ) {
if ( this . _getCurrentSelectionMode ( ) !== 'range' ) {
return ;
}
var $currentTr = $ ( e . target ) . closest ( 'tr' ) ;
var $lastTr = $ ( this . _lastChecked ) ;
var lastIndex = $lastTr . index ( ) ;
var currentIndex = $currentTr . index ( ) ;
var $rows = this . $fileList . children ( 'tr' ) ;
// last clicked checkbox below current one ?
if ( lastIndex > currentIndex ) {
var aux = lastIndex ;
lastIndex = currentIndex ;
currentIndex = aux ;
}
// auto-select everything in-between
this . _removeHalfSelection ( ) ;
for ( var i = 0 ; i <= $rows . length ; i ++ ) {
var $tr = $rows . eq ( i ) ;
var $checkbox = $tr . find ( 'td.selection>.selectCheckBox' ) ;
if ( lastIndex <= i && i <= currentIndex ) {
$tr . addClass ( 'halfselected' ) ;
$checkbox . prop ( 'checked' , true ) ;
}
}
} ,
_removeHalfSelection : function ( ) {
var $rows = this . $fileList . children ( 'tr' ) ;
for ( var i = 0 ; i <= $rows . length ; i ++ ) {
var $tr = $rows . eq ( i ) ;
$tr . removeClass ( 'halfselected' ) ;
var $checkbox = $tr . find ( 'td.selection>.selectCheckBox' ) ;
$checkbox . prop ( 'checked' , ! ! this . _selectedFiles [ $tr . data ( 'id' ) ] ) ;
}
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Event handler for when clicking on files to select them
* /
_onClickFile : function ( event ) {
var $tr = $ ( event . target ) . closest ( 'tr' ) ;
2015-09-25 10:23:28 +00:00
if ( $tr . hasClass ( 'dragging' ) ) {
return ;
}
2015-01-15 17:16:27 +00:00
if ( this . _allowSelection && ( event . ctrlKey || event . shiftKey ) ) {
2014-05-08 20:06:30 +00:00
event . preventDefault ( ) ;
if ( event . shiftKey ) {
2019-01-13 16:15:43 +00:00
this . _selectRange ( $tr ) ;
} else {
this . _selectSingle ( $tr ) ;
2014-05-08 20:06:30 +00:00
}
2019-01-13 16:15:43 +00:00
this . _lastChecked = $tr ;
2014-05-08 20:06:30 +00:00
this . updateSelectionSummary ( ) ;
} else {
2015-07-15 14:09:00 +00:00
// clicked directly on the name
2019-09-02 13:11:52 +00:00
if ( ! this . _detailsView || $ ( event . target ) . is ( '.nametext, .name, .thumbnail' ) || $ ( event . target ) . closest ( '.nametext' ) . length ) {
2015-07-15 14:09:00 +00:00
var filename = $tr . attr ( 'data-file' ) ;
var renaming = $tr . data ( 'renaming' ) ;
if ( ! renaming ) {
this . fileActions . currentFile = $tr . find ( 'td' ) ;
var mime = this . fileActions . getCurrentMimeType ( ) ;
var type = this . fileActions . getCurrentType ( ) ;
var permissions = this . fileActions . getCurrentPermissions ( ) ;
var action = this . fileActions . getDefault ( mime , type , permissions ) ;
if ( action ) {
event . preventDefault ( ) ;
action ( filename , {
$file : $tr ,
fileList : this ,
fileActions : this . fileActions ,
dir : $tr . attr ( 'data-path' ) || this . getCurrentDirectory ( )
} ) ;
}
// deselect row
$ ( event . target ) . closest ( 'a' ) . blur ( ) ;
2014-05-08 20:06:30 +00:00
}
2015-07-15 14:09:00 +00:00
} else {
2017-12-20 23:36:40 +00:00
// Even if there is no Details action the default event
// handler is prevented for consistency (although there
// should always be a Details action); otherwise the link
// would be downloaded by the browser when the user expected
// the details to be shown.
2015-07-15 14:09:00 +00:00
event . preventDefault ( ) ;
2017-12-20 23:36:40 +00:00
var filename = $tr . attr ( 'data-file' ) ;
2017-12-23 12:58:43 +00:00
this . fileActions . currentFile = $tr . find ( 'td' ) ;
2017-12-20 23:36:40 +00:00
var mime = this . fileActions . getCurrentMimeType ( ) ;
var type = this . fileActions . getCurrentType ( ) ;
var permissions = this . fileActions . getCurrentPermissions ( ) ;
var action = this . fileActions . get ( mime , type , permissions ) [ 'Details' ] ;
if ( action ) {
action ( filename , {
$file : $tr ,
fileList : this ,
fileActions : this . fileActions ,
dir : $tr . attr ( 'data-path' ) || this . getCurrentDirectory ( )
} ) ;
}
2014-02-12 13:50:23 +00:00
}
}
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Event handler for when clicking on a file ' s checkbox
* /
_onClickFileCheckbox : function ( e ) {
var $tr = $ ( e . target ) . closest ( 'tr' ) ;
2019-01-13 16:15:43 +00:00
if ( this . _getCurrentSelectionMode ( ) === 'range' ) {
this . _selectRange ( $tr ) ;
} else {
this . _selectSingle ( $tr ) ;
}
2014-05-08 20:06:30 +00:00
this . _lastChecked = $tr ;
this . updateSelectionSummary ( ) ;
2016-02-15 13:58:44 +00:00
if ( this . _detailsView && ! this . _detailsView . $el . hasClass ( 'disappear' ) ) {
// hide sidebar
this . _updateDetailsView ( null ) ;
2015-09-25 10:23:28 +00:00
}
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Event handler for when selecting / deselecting all files
* /
_onClickSelectAll : function ( e ) {
2019-04-30 10:26:37 +00:00
var hiddenFiles = this . $fileList . find ( 'tr.hidden' ) ;
var checked = e . target . checked ;
if ( hiddenFiles . length > 0 ) {
// set indeterminate alongside checked
e . target . indeterminate = checked ;
} else {
e . target . indeterminate = false
}
2018-06-19 23:15:22 +00:00
// Select only visible checkboxes to filter out unmatched file in search
this . $fileList . find ( 'td.selection > .selectCheckBox:visible' ) . prop ( 'checked' , checked )
2014-05-08 20:06:30 +00:00
. closest ( 'tr' ) . toggleClass ( 'selected' , checked ) ;
2018-06-19 23:15:22 +00:00
2014-05-08 20:06:30 +00:00
if ( checked ) {
for ( var i = 0 ; i < this . files . length ; i ++ ) {
2018-06-20 09:02:03 +00:00
// a search will automatically hide the unwanted rows
// let's only select the matches
var fileData = this . files [ i ] ;
2019-04-30 10:26:37 +00:00
var fileRow = this . $fileList . find ( 'tr[data-id=' + fileData . id + ']' ) ;
2018-06-20 09:02:03 +00:00
// do not select already selected ones
if ( ! fileRow . hasClass ( 'hidden' ) && _ . isUndefined ( this . _selectedFiles [ fileData . id ] ) ) {
2018-06-19 23:15:22 +00:00
this . _selectedFiles [ fileData . id ] = fileData ;
this . _selectionSummary . add ( fileData ) ;
}
2014-05-08 20:06:30 +00:00
}
2018-06-20 09:02:03 +00:00
} else {
// if we have some hidden row, then we're in a search
// Let's only deselect the visible ones
if ( hiddenFiles . length > 0 ) {
var visibleFiles = this . $fileList . find ( 'tr:not(.hidden)' ) ;
var self = this ;
visibleFiles . each ( function ( ) {
var id = parseInt ( $ ( this ) . data ( 'id' ) ) ;
// do not deselect already deselected ones
if ( ! _ . isUndefined ( self . _selectedFiles [ id ] ) ) {
// a search will automatically hide the unwanted rows
// let's only select the matches
2018-06-20 20:37:59 +00:00
var fileData = self . _selectedFiles [ id ] ;
2018-06-20 09:02:03 +00:00
delete self . _selectedFiles [ fileData . id ] ;
self . _selectionSummary . remove ( fileData ) ;
}
} ) ;
} else {
this . _selectedFiles = { } ;
this . _selectionSummary . clear ( ) ;
}
2014-04-04 16:46:08 +00:00
}
2014-04-08 15:09:57 +00:00
this . updateSelectionSummary ( ) ;
2016-02-15 13:58:44 +00:00
if ( this . _detailsView && ! this . _detailsView . $el . hasClass ( 'disappear' ) ) {
// hide sidebar
this . _updateDetailsView ( null ) ;
}
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Event handler for when clicking on "Download" for the selected files
* /
2018-06-06 18:01:25 +00:00
_onClickDownloadSelected : function ( event ) {
2014-05-08 20:06:30 +00:00
var files ;
2018-06-02 15:15:19 +00:00
var self = this ;
2014-05-08 20:06:30 +00:00
var dir = this . getCurrentDirectory ( ) ;
2018-06-02 15:15:19 +00:00
2016-05-24 12:09:05 +00:00
if ( this . isAllSelected ( ) && this . getSelectedFiles ( ) . length > 1 ) {
2014-05-08 20:06:30 +00:00
files = OC . basename ( dir ) ;
dir = OC . dirname ( dir ) || '/' ;
}
else {
files = _ . pluck ( this . getSelectedFiles ( ) , 'name' ) ;
}
2015-06-29 06:52:37 +00:00
// don't allow a second click on the download action
2018-06-02 15:15:19 +00:00
if ( this . fileMultiSelectMenu . isDisabled ( 'download' ) ) {
return false ;
2015-06-29 06:52:37 +00:00
}
2018-06-02 15:15:19 +00:00
this . fileMultiSelectMenu . toggleLoading ( 'download' , true ) ;
2015-07-14 15:18:31 +00:00
var disableLoadingState = function ( ) {
2018-06-02 15:15:19 +00:00
self . fileMultiSelectMenu . toggleLoading ( 'download' , false ) ;
2015-07-14 15:18:31 +00:00
} ;
2015-06-29 06:52:37 +00:00
2016-01-10 16:56:18 +00:00
if ( this . getSelectedFiles ( ) . length > 1 ) {
OCA . Files . Files . handleDownload ( this . getDownloadUrl ( files , dir , true ) , disableLoadingState ) ;
}
else {
2017-08-27 13:20:18 +00:00
var first = this . getSelectedFiles ( ) [ 0 ] ;
2016-01-10 16:56:18 +00:00
OCA . Files . Files . handleDownload ( this . getDownloadUrl ( first . name , dir , true ) , disableLoadingState ) ;
}
2018-06-06 18:01:25 +00:00
event . preventDefault ( ) ;
2014-05-08 20:06:30 +00:00
} ,
2017-08-27 13:20:18 +00:00
/ * *
* Event handler for when clicking on "Move" for the selected files
* /
2018-06-06 18:01:25 +00:00
_onClickCopyMoveSelected : function ( event ) {
2017-08-27 13:20:18 +00:00
var files ;
var self = this ;
files = _ . pluck ( this . getSelectedFiles ( ) , 'name' ) ;
// don't allow a second click on the download action
2018-06-06 18:01:25 +00:00
if ( this . fileMultiSelectMenu . isDisabled ( 'copyMove' ) ) {
2018-06-02 15:15:19 +00:00
return false ;
2017-08-27 13:20:18 +00:00
}
var disableLoadingState = function ( ) {
2018-06-06 18:01:25 +00:00
self . fileMultiSelectMenu . toggleLoading ( 'copyMove' , false ) ;
2017-08-27 13:20:18 +00:00
} ;
2018-02-06 15:44:04 +00:00
var actions = this . isSelectedMovable ( ) ? OC . dialogs . FILEPICKER _TYPE _COPY _MOVE : OC . dialogs . FILEPICKER _TYPE _COPY ;
2018-12-23 13:27:08 +00:00
var dialogDir = self . getCurrentDirectory ( ) ;
2019-01-07 21:07:16 +00:00
if ( typeof self . dirInfo . dirLastCopiedTo !== 'undefined' ) {
2018-12-23 13:27:08 +00:00
dialogDir = self . dirInfo . dirLastCopiedTo ;
}
2018-10-01 18:32:37 +00:00
OC . dialogs . filepicker ( t ( 'files' , 'Choose target folder' ) , function ( targetPath , type ) {
2018-06-15 21:05:29 +00:00
self . fileMultiSelectMenu . toggleLoading ( 'copyMove' , true ) ;
2017-08-27 15:39:22 +00:00
if ( type === OC . dialogs . FILEPICKER _TYPE _COPY ) {
self . copy ( files , targetPath , disableLoadingState ) ;
}
if ( type === OC . dialogs . FILEPICKER _TYPE _MOVE ) {
self . move ( files , targetPath , disableLoadingState ) ;
}
2019-10-14 17:19:16 +00:00
self . dirInfo . dirLastCopiedTo = targetPath ;
2018-12-23 13:27:08 +00:00
} , false , "httpd/unix-directory" , true , actions , dialogDir ) ;
2018-06-06 18:01:25 +00:00
event . preventDefault ( ) ;
2017-08-27 13:20:18 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Event handler for when clicking on "Delete" for the selected files
* /
2018-06-06 18:01:25 +00:00
_onClickDeleteSelected : function ( event ) {
2014-05-08 20:06:30 +00:00
var files = null ;
if ( ! this . isAllSelected ( ) ) {
files = _ . pluck ( this . getSelectedFiles ( ) , 'name' ) ;
}
this . do _delete ( files ) ;
event . preventDefault ( ) ;
} ,
/ * *
* Event handler when clicking on a table header
* /
_onClickHeader : function ( e ) {
2015-09-29 11:24:33 +00:00
if ( this . $table . hasClass ( 'multiselect' ) ) {
return ;
}
2014-05-08 20:06:30 +00:00
var $target = $ ( e . target ) ;
var sort ;
if ( ! $target . is ( 'a' ) ) {
$target = $target . closest ( 'a' ) ;
}
sort = $target . attr ( 'data-sort' ) ;
2016-07-22 12:19:04 +00:00
if ( sort && this . _allowSorting ) {
2014-05-08 20:06:30 +00:00
if ( this . _sort === sort ) {
2016-04-12 09:51:50 +00:00
this . setSort ( sort , ( this . _sortDirection === 'desc' ) ? 'asc' : 'desc' , true , true ) ;
2014-02-12 13:50:23 +00:00
}
2014-05-08 20:06:30 +00:00
else {
2014-07-17 19:31:54 +00:00
if ( sort === 'name' ) { //default sorting of name is opposite to size and mtime
2016-04-12 09:51:50 +00:00
this . setSort ( sort , 'asc' , true , true ) ;
2014-07-08 17:52:28 +00:00
}
else {
2016-04-12 09:51:50 +00:00
this . setSort ( sort , 'desc' , true , true ) ;
2014-07-08 17:52:28 +00:00
}
2014-05-08 20:06:30 +00:00
}
2014-02-12 13:50:23 +00:00
}
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Event handler when clicking on a bread crumb
* /
_onClickBreadCrumb : function ( e ) {
2017-11-07 04:53:17 +00:00
// Select a crumb or a crumb in the menu
var $el = $ ( e . target ) . closest ( '.crumb, .crumblist' ) ,
2014-05-08 20:06:30 +00:00
$targetDir = $el . data ( 'dir' ) ;
2015-03-24 21:05:02 +00:00
if ( $targetDir !== undefined && e . which === 1 ) {
2014-05-08 20:06:30 +00:00
e . preventDefault ( ) ;
2017-09-17 06:29:22 +00:00
this . changeDirectory ( $targetDir , true , true ) ;
2015-03-24 21:05:02 +00:00
this . updateSearch ( ) ;
2014-05-08 20:06:30 +00:00
}
} ,
2014-02-12 13:50:23 +00:00
2014-05-12 17:54:20 +00:00
/ * *
* Event handler for when scrolling the list container .
* This appends / renders the next page of entries when reaching the bottom .
* /
2014-05-08 20:06:30 +00:00
_onScroll : function ( e ) {
2014-08-07 22:27:31 +00:00
if ( this . $container . scrollTop ( ) + this . $container . height ( ) > this . $el . height ( ) - 300 ) {
2014-05-08 20:06:30 +00:00
this . _nextPage ( true ) ;
}
} ,
/ * *
* Event handler when dropping on a breadcrumb
* /
_onDropOnBreadCrumb : function ( event , ui ) {
2015-08-17 14:03:05 +00:00
var self = this ;
2014-05-08 20:06:30 +00:00
var $target = $ ( event . target ) ;
2017-11-08 18:03:32 +00:00
if ( ! $target . is ( '.crumb, .crumblist' ) ) {
$target = $target . closest ( '.crumb, .crumblist' ) ;
2014-05-08 20:06:30 +00:00
}
var targetPath = $ ( event . target ) . data ( 'dir' ) ;
var dir = this . getCurrentDirectory ( ) ;
while ( dir . substr ( 0 , 1 ) === '/' ) { //remove extra leading /'s
dir = dir . substr ( 1 ) ;
}
dir = '/' + dir ;
if ( dir . substr ( - 1 , 1 ) !== '/' ) {
dir = dir + '/' ;
}
// do nothing if dragged on current dir
if ( targetPath === dir || targetPath + '/' === dir ) {
return ;
}
2014-02-12 13:50:23 +00:00
2014-05-08 20:06:30 +00:00
var files = this . getSelectedFiles ( ) ;
if ( files . length === 0 ) {
// single one selected without checkbox?
2015-08-17 14:03:05 +00:00
files = _ . map ( ui . helper . find ( 'tr' ) , function ( el ) {
return self . elementToFile ( $ ( el ) ) ;
} ) ;
2014-04-04 16:46:08 +00:00
}
2014-02-12 13:50:23 +00:00
2018-11-03 23:01:32 +00:00
var movePromise = this . move ( _ . pluck ( files , 'name' ) , targetPath ) ;
2015-11-04 10:44:47 +00:00
// re-enable td elements to be droppable
// sometimes the filename drop handler is still called after re-enable,
// it seems that waiting for a short time before re-enabling solves the problem
setTimeout ( function ( ) {
self . $el . find ( 'td.filename.ui-droppable' ) . droppable ( 'enable' ) ;
} , 10 ) ;
2018-11-03 23:01:32 +00:00
return movePromise ;
2014-05-08 20:06:30 +00:00
} ,
2014-02-12 13:50:23 +00:00
2014-05-08 20:06:30 +00:00
/ * *
* Sets a new page title
* /
setPageTitle : function ( title ) {
if ( title ) {
title += ' - ' ;
} else {
title = '' ;
}
title += this . appName ;
2017-04-12 04:16:27 +00:00
// Sets the page title with the " - Nextcloud" suffix as in templates
2019-05-14 09:14:29 +00:00
window . document . title = title + ' - ' + OC . theme . title ;
2013-10-28 19:22:06 +00:00
2014-05-08 20:06:30 +00:00
return true ;
} ,
/ * *
2015-09-28 15:50:11 +00:00
* Returns the file info for the given file name from the internal collection .
*
* @ param { string } fileName file name
* @ return { OCA . Files . FileInfo } file info or null if it was not found
*
* @ since 8.2
* /
findFile : function ( fileName ) {
return _ . find ( this . files , function ( aFile ) {
return ( aFile . name === fileName ) ;
} ) || null ;
} ,
/ * *
* Returns the tr element for a given file name , but only if it was already rendered .
*
* @ param { string } fileName file name
* @ return { Object } jQuery object of the matching row
2014-05-08 20:06:30 +00:00
* /
findFileEl : function ( fileName ) {
// use filterAttr to avoid escaping issues
return this . $fileList . find ( 'tr' ) . filterAttr ( 'data-file' , fileName ) ;
} ,
/ * *
* Returns the file data from a given file element .
* @ param $el file tr element
* @ return file data
* /
elementToFile : function ( $el ) {
$el = $ ( $el ) ;
2015-10-28 16:43:36 +00:00
var data = {
2014-05-08 20:06:30 +00:00
id : parseInt ( $el . attr ( 'data-id' ) , 10 ) ,
name : $el . attr ( 'data-file' ) ,
mimetype : $el . attr ( 'data-mime' ) ,
2015-08-24 15:16:20 +00:00
mtime : parseInt ( $el . attr ( 'data-mtime' ) , 10 ) ,
2014-05-08 20:06:30 +00:00
type : $el . attr ( 'data-type' ) ,
2014-11-20 15:53:32 +00:00
etag : $el . attr ( 'data-etag' ) ,
2016-07-26 11:51:18 +00:00
permissions : parseInt ( $el . attr ( 'data-permissions' ) , 10 ) ,
2017-11-20 19:51:32 +00:00
hasPreview : $el . attr ( 'data-has-preview' ) === 'true' ,
isEncrypted : $el . attr ( 'data-e2eencrypted' ) === 'true'
2014-05-08 20:06:30 +00:00
} ;
2016-11-21 14:03:45 +00:00
var size = $el . attr ( 'data-size' ) ;
if ( size ) {
data . size = parseInt ( size , 10 ) ;
}
2015-10-28 16:43:36 +00:00
var icon = $el . attr ( 'data-icon' ) ;
if ( icon ) {
data . icon = icon ;
}
var mountType = $el . attr ( 'data-mounttype' ) ;
if ( mountType ) {
data . mountType = mountType ;
}
2016-02-08 16:02:05 +00:00
var path = $el . attr ( 'data-path' ) ;
if ( path ) {
data . path = path ;
}
2015-10-28 16:43:36 +00:00
return data ;
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Appends the next page of files into the table
* @ param animate true to animate the new elements
2014-07-01 19:32:04 +00:00
* @ return array of DOM elements of the newly added files
2014-05-08 20:06:30 +00:00
* /
_nextPage : function ( animate ) {
var index = this . $fileList . children ( ) . length ,
2014-10-11 13:10:54 +00:00
count = this . pageSize ( ) ,
2014-12-18 09:26:41 +00:00
hidden ,
2014-05-08 20:06:30 +00:00
tr ,
fileData ,
newTrs = [ ] ,
2016-11-07 12:04:39 +00:00
isAllSelected = this . isAllSelected ( ) ,
showHidden = this . _filesConfig . get ( 'showhidden' ) ;
2014-05-08 20:06:30 +00:00
if ( index >= this . files . length ) {
2014-07-01 19:32:04 +00:00
return false ;
2014-04-03 18:57:06 +00:00
}
2014-05-08 20:06:30 +00:00
while ( count > 0 && index < this . files . length ) {
fileData = this . files [ index ] ;
2014-12-18 09:26:41 +00:00
if ( this . _filter ) {
hidden = fileData . name . toLowerCase ( ) . indexOf ( this . _filter . toLowerCase ( ) ) === - 1 ;
} else {
hidden = false ;
}
tr = this . _renderRow ( fileData , { updateSummary : false , silent : true , hidden : hidden } ) ;
2014-05-08 20:06:30 +00:00
this . $fileList . append ( tr ) ;
if ( isAllSelected || this . _selectedFiles [ fileData . id ] ) {
tr . addClass ( 'selected' ) ;
2014-12-11 17:20:04 +00:00
tr . find ( '.selectCheckBox' ) . prop ( 'checked' , true ) ;
2014-05-08 20:06:30 +00:00
}
if ( animate ) {
tr . addClass ( 'appear transparent' ) ;
}
2014-07-01 19:32:04 +00:00
newTrs . push ( tr ) ;
2014-05-08 20:06:30 +00:00
index ++ ;
2016-11-07 12:04:39 +00:00
// only count visible rows
if ( showHidden || ! tr . hasClass ( 'hidden-file' ) ) {
count -- ;
}
2014-04-03 18:57:06 +00:00
}
2014-07-01 19:32:04 +00:00
// trigger event for newly added rows
if ( newTrs . length > 0 ) {
this . $fileList . trigger ( $ . Event ( 'fileActionsReady' , { fileList : this , $files : newTrs } ) ) ;
}
2014-05-08 20:06:30 +00:00
if ( animate ) {
// defer, for animation
window . setTimeout ( function ( ) {
for ( var i = 0 ; i < newTrs . length ; i ++ ) {
newTrs [ i ] . removeClass ( 'transparent' ) ;
}
} , 0 ) ;
}
2016-04-12 15:10:09 +00:00
2014-07-01 19:32:04 +00:00
return newTrs ;
2014-05-08 20:06:30 +00:00
} ,
2013-10-28 19:22:06 +00:00
2014-06-27 11:36:18 +00:00
/ * *
* Event handler for when file actions were updated .
* This will refresh the file actions on the list .
* /
_onFileActionsUpdated : function ( ) {
var self = this ;
2014-07-01 19:32:04 +00:00
var $files = this . $fileList . find ( 'tr' ) ;
if ( ! $files . length ) {
return ;
}
$files . each ( function ( ) {
self . fileActions . display ( $ ( this ) . find ( 'td.filename' ) , false , self ) ;
2014-06-27 11:36:18 +00:00
} ) ;
2014-07-01 19:32:04 +00:00
this . $fileList . trigger ( $ . Event ( 'fileActionsReady' , { fileList : this , $files : $files } ) ) ;
2014-06-27 11:36:18 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Sets the files to be displayed in the list .
* This operation will re - render the list and update the summary .
* @ param filesArray array of file data ( map )
* /
setFiles : function ( filesArray ) {
2015-03-27 00:34:55 +00:00
var self = this ;
2014-05-08 20:06:30 +00:00
// detach to make adding multiple rows faster
this . files = filesArray ;
2013-10-28 19:22:06 +00:00
2014-05-08 20:06:30 +00:00
this . $fileList . empty ( ) ;
2014-02-11 14:57:45 +00:00
2017-09-28 23:36:10 +00:00
if ( this . _allowSelection ) {
// The results table, which has no selection column, checks
// whether the main table has a selection column or not in order
// to align its contents with those of the main table.
this . $el . addClass ( 'has-selection' ) ;
}
2014-05-08 20:06:30 +00:00
// clear "Select all" checkbox
2014-05-12 17:54:20 +00:00
this . $el . find ( '.select-all' ) . prop ( 'checked' , false ) ;
2014-04-11 10:46:12 +00:00
2016-04-12 15:10:09 +00:00
// Save full files list while rendering
2014-05-08 20:06:30 +00:00
this . isEmpty = this . files . length === 0 ;
this . _nextPage ( ) ;
2014-04-11 10:46:12 +00:00
2014-05-08 20:06:30 +00:00
this . updateEmptyContent ( ) ;
2014-02-20 14:36:52 +00:00
2016-04-12 15:10:09 +00:00
this . fileSummary . calculate ( this . files ) ;
2014-02-11 14:57:45 +00:00
2014-06-23 10:55:42 +00:00
this . _selectedFiles = { } ;
this . _selectionSummary . clear ( ) ;
2014-05-08 20:06:30 +00:00
this . updateSelectionSummary ( ) ;
$ ( window ) . scrollTop ( 0 ) ;
2015-03-27 00:34:55 +00:00
this . $fileList . trigger ( jQuery . Event ( 'updated' ) ) ;
_ . defer ( function ( ) {
self . $el . closest ( '#app-content' ) . trigger ( jQuery . Event ( 'apprendered' ) ) ;
} ) ;
2014-05-08 20:06:30 +00:00
} ,
2015-07-13 15:38:13 +00:00
2016-04-12 15:10:09 +00:00
/ * *
2016-08-17 15:34:15 +00:00
* Returns whether the given file info must be hidden
2016-04-12 15:10:09 +00:00
*
2016-08-17 15:34:15 +00:00
* @ param { OC . Files . FileInfo } fileInfo file info
2016-11-21 14:03:45 +00:00
*
2016-08-17 15:34:15 +00:00
* @ return { boolean } true if the file is a hidden file , false otherwise
2016-04-12 15:10:09 +00:00
* /
2016-08-17 15:34:15 +00:00
_isHiddenFile : function ( file ) {
return file . name && file . name . charAt ( 0 ) === '.' ;
2016-04-12 15:10:09 +00:00
} ,
2015-07-13 15:38:13 +00:00
/ * *
* Returns the icon URL matching the given file info
*
* @ param { OC . Files . FileInfo } fileInfo file info
*
* @ return { string } icon URL
* /
_getIconUrl : function ( fileInfo ) {
var mimeType = fileInfo . mimetype || 'application/octet-stream' ;
if ( mimeType === 'httpd/unix-directory' ) {
// use default folder icon
if ( fileInfo . mountType === 'shared' || fileInfo . mountType === 'shared-root' ) {
return OC . MimeType . getIconUrl ( 'dir-shared' ) ;
} else if ( fileInfo . mountType === 'external-root' ) {
return OC . MimeType . getIconUrl ( 'dir-external' ) ;
2017-04-28 08:03:31 +00:00
} else if ( fileInfo . mountType !== undefined && fileInfo . mountType !== '' ) {
2017-04-26 12:53:11 +00:00
return OC . MimeType . getIconUrl ( 'dir-' + fileInfo . mountType ) ;
2015-07-13 15:38:13 +00:00
}
return OC . MimeType . getIconUrl ( 'dir' ) ;
}
return OC . MimeType . getIconUrl ( mimeType ) ;
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Creates a new table row element using the given file data .
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileData file info attributes
2014-06-23 21:56:10 +00:00
* @ param options map of attributes
2014-05-08 20:06:30 +00:00
* @ return new tr element ( not appended to the table )
* /
_createRow : function ( fileData , options ) {
var td , simpleSize , basename , extension , sizeColor ,
2015-07-13 15:38:13 +00:00
icon = fileData . icon || this . _getIconUrl ( fileData ) ,
2014-05-08 20:06:30 +00:00
name = fileData . name ,
2015-07-13 15:38:13 +00:00
// TODO: get rid of type, only use mime type
2014-05-08 20:06:30 +00:00
type = fileData . type || 'file' ,
2014-12-05 13:54:43 +00:00
mtime = parseInt ( fileData . mtime , 10 ) ,
2014-05-08 20:06:30 +00:00
mime = fileData . mimetype ,
2014-05-21 10:54:34 +00:00
path = fileData . path ,
2015-10-28 16:43:36 +00:00
dataIcon = null ,
2014-05-08 20:06:30 +00:00
linkUrl ;
options = options || { } ;
2014-12-05 13:54:43 +00:00
if ( isNaN ( mtime ) ) {
2015-10-28 16:43:36 +00:00
mtime = new Date ( ) . getTime ( ) ;
2014-12-05 13:54:43 +00:00
}
2014-05-08 20:06:30 +00:00
if ( type === 'dir' ) {
mime = mime || 'httpd/unix-directory' ;
2015-07-17 12:30:12 +00:00
2017-09-19 15:24:53 +00:00
if ( fileData . isEncrypted ) {
icon = OC . MimeType . getIconUrl ( 'dir-encrypted' ) ;
dataIcon = icon ;
} else if ( fileData . mountType && fileData . mountType . indexOf ( 'external' ) === 0 ) {
2015-07-17 12:30:12 +00:00
icon = OC . MimeType . getIconUrl ( 'dir-external' ) ;
2015-10-28 16:43:36 +00:00
dataIcon = icon ;
2015-07-17 12:30:12 +00:00
}
2014-05-08 20:06:30 +00:00
}
2014-02-12 13:50:23 +00:00
2017-11-02 13:55:58 +00:00
var permissions = fileData . permissions ;
if ( permissions === undefined || permissions === null ) {
permissions = this . getDirectoryPermissions ( ) ;
}
2014-05-08 20:06:30 +00:00
//containing tr
var tr = $ ( '<tr></tr>' ) . attr ( {
"data-id" : fileData . id ,
"data-type" : type ,
"data-size" : fileData . size ,
"data-file" : name ,
"data-mime" : mime ,
"data-mtime" : mtime ,
"data-etag" : fileData . etag ,
2017-11-02 13:55:58 +00:00
"data-permissions" : permissions ,
2017-11-20 19:51:32 +00:00
"data-has-preview" : fileData . hasPreview !== false ,
"data-e2eencrypted" : fileData . isEncrypted === true
2014-05-08 20:06:30 +00:00
} ) ;
2014-02-11 14:57:45 +00:00
2015-10-28 16:43:36 +00:00
if ( dataIcon ) {
// icon override
tr . attr ( 'data-icon' , dataIcon ) ;
}
2014-07-10 15:25:46 +00:00
if ( fileData . mountType ) {
2015-12-14 09:44:47 +00:00
// dirInfo (parent) only exist for the "real" file list
if ( this . dirInfo . id ) {
// FIXME: HACK: detect shared-root
if ( fileData . mountType === 'shared' && this . dirInfo . mountType !== 'shared' && this . dirInfo . mountType !== 'shared-root' ) {
// if parent folder isn't share, assume the displayed folder is a share root
fileData . mountType = 'shared-root' ;
} else if ( fileData . mountType === 'external' && this . dirInfo . mountType !== 'external' && this . dirInfo . mountType !== 'external-root' ) {
// if parent folder isn't external, assume the displayed folder is the external storage root
fileData . mountType = 'external-root' ;
}
2015-07-13 15:38:13 +00:00
}
2014-07-10 15:25:46 +00:00
tr . attr ( 'data-mounttype' , fileData . mountType ) ;
}
2014-05-21 10:54:34 +00:00
if ( ! _ . isUndefined ( path ) ) {
tr . attr ( 'data-path' , path ) ;
}
else {
path = this . getCurrentDirectory ( ) ;
}
2017-09-28 23:36:10 +00:00
// selection td
if ( this . _allowSelection ) {
td = $ ( '<td class="selection"></td>' ) ;
td . append (
'<input id="select-' + this . id + '-' + fileData . id +
'" type="checkbox" class="selectCheckBox checkbox"/><label for="select-' + this . id + '-' + fileData . id + '">' +
'<span class="hidden-visually">' + t ( 'files' , 'Select' ) + '</span>' +
'</label>'
) ;
tr . append ( td ) ;
}
2014-05-08 20:06:30 +00:00
// filename td
2014-12-11 16:36:14 +00:00
td = $ ( '<td class="filename"></td>' ) ;
2014-02-11 14:57:45 +00:00
2014-05-08 20:06:30 +00:00
// linkUrl
2015-07-13 15:38:13 +00:00
if ( mime === 'httpd/unix-directory' ) {
2014-04-30 15:42:35 +00:00
linkUrl = this . linkTo ( path + '/' + name ) ;
2014-05-08 20:06:30 +00:00
}
else {
2015-07-13 15:38:13 +00:00
linkUrl = this . getDownloadUrl ( name , path , type === 'dir' ) ;
2014-05-08 20:06:30 +00:00
}
var linkElem = $ ( '<a></a>' ) . attr ( {
"class" : "name" ,
"href" : linkUrl
} ) ;
2013-10-28 19:22:06 +00:00
2017-09-28 12:42:37 +00:00
linkElem . append ( '<div class="thumbnail-wrapper"><div class="thumbnail" style="background-image:url(' + icon + ');"></div></div>' ) ;
2014-05-08 20:06:30 +00:00
// from here work on the display name
name = fileData . displayName || name ;
2013-10-28 19:22:06 +00:00
2015-03-26 09:29:01 +00:00
// show hidden files (starting with a dot) completely in gray
if ( name . indexOf ( '.' ) === 0 ) {
basename = '' ;
extension = name ;
2014-05-08 20:06:30 +00:00
// split extension from filename for non dirs
2015-07-13 15:38:13 +00:00
} else if ( mime !== 'httpd/unix-directory' && name . indexOf ( '.' ) !== - 1 ) {
2014-05-08 20:06:30 +00:00
basename = name . substr ( 0 , name . lastIndexOf ( '.' ) ) ;
extension = name . substr ( name . lastIndexOf ( '.' ) ) ;
} else {
basename = name ;
extension = false ;
}
2014-06-06 12:00:18 +00:00
var nameSpan = $ ( '<span></span>' ) . addClass ( 'nametext' ) ;
2014-06-07 18:24:21 +00:00
var innernameSpan = $ ( '<span></span>' ) . addClass ( 'innernametext' ) . text ( basename ) ;
2016-10-19 09:33:25 +00:00
2017-10-16 14:15:15 +00:00
var conflictingItems = this . $fileList . find ( 'tr[data-file="' + this . _jqSelEscape ( name ) + '"]' ) ;
if ( conflictingItems . length !== 0 ) {
if ( conflictingItems . length === 1 ) {
// Update the path on the first conflicting item
var $firstConflict = $ ( conflictingItems [ 0 ] ) ,
firstConflictPath = $firstConflict . attr ( 'data-path' ) + '/' ;
if ( firstConflictPath . charAt ( 0 ) === '/' ) {
firstConflictPath = firstConflictPath . substr ( 1 ) ;
2016-10-19 09:33:25 +00:00
}
2017-10-18 13:16:51 +00:00
if ( firstConflictPath && firstConflictPath !== '/' ) {
$firstConflict . find ( 'td.filename span.innernametext' ) . prepend ( $ ( '<span></span>' ) . addClass ( 'conflict-path' ) . text ( firstConflictPath ) ) ;
}
2017-10-16 14:15:15 +00:00
}
var conflictPath = path + '/' ;
if ( conflictPath . charAt ( 0 ) === '/' ) {
conflictPath = conflictPath . substr ( 1 ) ;
}
if ( path && path !== '/' ) {
2016-10-19 09:33:25 +00:00
nameSpan . append ( $ ( '<span></span>' ) . addClass ( 'conflict-path' ) . text ( conflictPath ) ) ;
}
}
2014-06-06 12:00:18 +00:00
nameSpan . append ( innernameSpan ) ;
2014-05-08 20:06:30 +00:00
linkElem . append ( nameSpan ) ;
if ( extension ) {
nameSpan . append ( $ ( '<span></span>' ) . addClass ( 'extension' ) . text ( extension ) ) ;
}
2014-08-27 09:28:31 +00:00
if ( fileData . extraData ) {
if ( fileData . extraData . charAt ( 0 ) === '/' ) {
fileData . extraData = fileData . extraData . substr ( 1 ) ;
}
nameSpan . addClass ( 'extra-data' ) . attr ( 'title' , fileData . extraData ) ;
2017-11-14 10:55:44 +00:00
nameSpan . tooltip ( { placement : 'top' } ) ;
2014-08-27 09:28:31 +00:00
}
2014-05-08 20:06:30 +00:00
// dirs can show the number of uploaded files
2015-12-17 10:50:24 +00:00
if ( mime === 'httpd/unix-directory' ) {
2014-05-08 20:06:30 +00:00
linkElem . append ( $ ( '<span></span>' ) . attr ( {
'class' : 'uploadtext' ,
'currentUploads' : 0
} ) ) ;
}
td . append ( linkElem ) ;
tr . append ( td ) ;
2014-02-12 13:50:23 +00:00
2019-03-21 07:42:27 +00:00
try {
var maxContrastHex = window . getComputedStyle ( document . documentElement )
2019-07-24 19:48:52 +00:00
. getPropertyValue ( '--color-text-maxcontrast' ) . trim ( )
2019-07-25 07:45:09 +00:00
if ( maxContrastHex . length < 4 ) {
throw Error ( ) ;
}
2019-03-21 07:42:27 +00:00
var maxContrast = parseInt ( maxContrastHex . substring ( 1 , 3 ) , 16 )
} catch ( error ) {
var maxContrast = OCA . Accessibility
&& OCA . Accessibility . theme === 'themedark'
2019-07-25 07:45:09 +00:00
? 130
: 118
2019-03-21 07:42:27 +00:00
}
2014-05-08 20:06:30 +00:00
// size column
if ( typeof ( fileData . size ) !== 'undefined' && fileData . size >= 0 ) {
2014-06-02 08:38:46 +00:00
simpleSize = humanFileSize ( parseInt ( fileData . size , 10 ) , true ) ;
2018-04-18 13:38:30 +00:00
// rgb(118, 118, 118) / #767676
// min. color contrast for normal text on white background according to WCAG AA
2019-03-21 07:42:27 +00:00
sizeColor = Math . round ( 118 - Math . pow ( ( fileData . size / ( 1024 * 1024 ) ) , 2 ) ) ;
// ensure that the brightest color is still readable
// min. color contrast for normal text on white background according to WCAG AA
if ( sizeColor >= maxContrast ) {
sizeColor = maxContrast ;
}
2018-10-14 11:48:39 +00:00
2018-10-16 18:42:06 +00:00
if ( OCA . Accessibility && OCA . Accessibility . theme === 'themedark' ) {
2018-10-14 11:48:39 +00:00
sizeColor = Math . abs ( sizeColor ) ;
2019-03-21 07:42:27 +00:00
// ensure that the dimmest color is still readable
// min. color contrast for normal text on black background according to WCAG AA
if ( sizeColor < maxContrast ) {
sizeColor = maxContrast ;
}
2018-10-14 11:48:39 +00:00
}
2014-05-08 20:06:30 +00:00
} else {
simpleSize = t ( 'files' , 'Pending' ) ;
}
2013-10-28 19:22:06 +00:00
2014-05-08 20:06:30 +00:00
td = $ ( '<td></td>' ) . attr ( {
"class" : "filesize" ,
"style" : 'color:rgb(' + sizeColor + ',' + sizeColor + ',' + sizeColor + ')'
} ) . text ( simpleSize ) ;
tr . append ( td ) ;
2014-07-02 15:20:56 +00:00
// date column (1000 milliseconds to seconds, 60 seconds, 60 minutes, 24 hours)
// difference in days multiplied by 5 - brightest shade for files older than 32 days (160/5)
var modifiedColor = Math . round ( ( ( new Date ( ) ) . getTime ( ) - mtime ) / 1000 / 60 / 60 / 24 * 5 ) ;
2019-03-21 07:42:27 +00:00
2014-07-02 14:37:15 +00:00
// ensure that the brightest color is still readable
2018-04-18 13:38:30 +00:00
// min. color contrast for normal text on white background according to WCAG AA
2019-03-21 07:42:27 +00:00
if ( modifiedColor >= maxContrast ) {
modifiedColor = maxContrast ;
2014-07-02 14:37:15 +00:00
}
2019-03-21 07:42:27 +00:00
2018-10-16 18:42:06 +00:00
if ( OCA . Accessibility && OCA . Accessibility . theme === 'themedark' ) {
2018-10-14 11:48:39 +00:00
modifiedColor = Math . abs ( modifiedColor ) ;
// ensure that the dimmest color is still readable
// min. color contrast for normal text on black background according to WCAG AA
2019-03-21 07:42:27 +00:00
if ( modifiedColor < maxContrast ) {
modifiedColor = maxContrast ;
2018-10-14 11:48:39 +00:00
}
}
2019-03-21 07:42:27 +00:00
2014-12-05 13:54:43 +00:00
var formatted ;
var text ;
if ( mtime > 0 ) {
2015-07-15 14:09:00 +00:00
formatted = OC . Util . formatDate ( mtime ) ;
2014-12-05 13:54:43 +00:00
text = OC . Util . relativeModifiedDate ( mtime ) ;
} else {
formatted = t ( 'files' , 'Unable to determine date' ) ;
text = '?' ;
}
2014-05-08 20:06:30 +00:00
td = $ ( '<td></td>' ) . attr ( { "class" : "date" } ) ;
td . append ( $ ( '<span></span>' ) . attr ( {
2016-08-31 15:33:00 +00:00
"class" : "modified live-relative-timestamp" ,
2014-12-05 13:54:43 +00:00
"title" : formatted ,
2016-08-31 15:33:00 +00:00
"data-timestamp" : mtime ,
2014-05-08 20:06:30 +00:00
"style" : 'color:rgb(' + modifiedColor + ',' + modifiedColor + ',' + modifiedColor + ')'
2015-07-27 13:56:03 +00:00
} ) . text ( text )
. tooltip ( { placement : 'top' } )
) ;
2014-05-08 20:06:30 +00:00
tr . find ( '.filesize' ) . text ( simpleSize ) ;
tr . append ( td ) ;
return tr ;
} ,
2016-12-20 08:32:47 +00:00
/* escape a selector expression for jQuery */
_jqSelEscape : function ( expression ) {
if ( expression ) {
return expression . replace ( /[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g , '\\$&' ) ;
}
return null ;
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Adds an entry to the files array and also into the DOM
* in a sorted manner .
*
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileData map of file attributes
2014-06-23 21:56:10 +00:00
* @ param { Object } [ options ] map of attributes
* @ param { boolean } [ options . updateSummary ] true to update the summary
* after adding ( default ) , false otherwise . Defaults to true .
* @ param { boolean } [ options . silent ] true to prevent firing events like "fileActionsReady" ,
* defaults to false .
* @ param { boolean } [ options . animate ] true to animate the thumbnail image after load
* defaults to true .
2014-05-08 20:06:30 +00:00
* @ return new tr element ( not appended to the table )
* /
add : function ( fileData , options ) {
2017-11-23 16:51:56 +00:00
var index ;
2014-05-08 20:06:30 +00:00
var $tr ;
var $rows ;
var $insertionPoint ;
2014-07-17 10:42:09 +00:00
options = _ . extend ( { animate : true } , options || { } ) ;
2014-05-08 20:06:30 +00:00
// there are three situations to cover:
// 1) insertion point is visible on the current page
// 2) insertion point is on a not visible page (visible after scrolling)
// 3) insertion point is at the end of the list
$rows = this . $fileList . children ( ) ;
index = this . _findInsertionIndex ( fileData ) ;
if ( index > this . files . length ) {
index = this . files . length ;
}
else {
$insertionPoint = $rows . eq ( index ) ;
}
2013-10-28 19:22:06 +00:00
2014-05-08 20:06:30 +00:00
// is the insertion point visible ?
if ( $insertionPoint . length ) {
// only render if it will really be inserted
$tr = this . _renderRow ( fileData , options ) ;
$insertionPoint . before ( $tr ) ;
}
else {
// if insertion point is after the last visible
// entry, append
if ( index === $rows . length ) {
$tr = this . _renderRow ( fileData , options ) ;
this . $fileList . append ( $tr ) ;
}
}
2014-02-11 15:52:56 +00:00
2014-05-08 20:06:30 +00:00
this . isEmpty = false ;
this . files . splice ( index , 0 , fileData ) ;
2014-02-11 15:52:56 +00:00
2014-05-08 20:06:30 +00:00
if ( $tr && options . animate ) {
$tr . addClass ( 'appear transparent' ) ;
window . setTimeout ( function ( ) {
$tr . removeClass ( 'transparent' ) ;
2018-03-01 12:50:37 +00:00
$ ( "#fileList tr" ) . removeClass ( 'mouseOver' ) ;
2018-03-01 11:49:07 +00:00
$tr . addClass ( 'mouseOver' ) ;
2014-05-08 20:06:30 +00:00
} ) ;
}
2013-10-28 19:22:06 +00:00
2014-09-04 10:33:38 +00:00
if ( options . scrollTo ) {
this . scrollTo ( fileData . name ) ;
}
2014-05-08 20:06:30 +00:00
// defaults to true if not defined
if ( typeof ( options . updateSummary ) === 'undefined' || ! ! options . updateSummary ) {
this . fileSummary . add ( fileData , true ) ;
this . updateEmptyContent ( ) ;
}
2014-04-17 13:54:45 +00:00
2014-05-08 20:06:30 +00:00
return $tr ;
} ,
/ * *
* Creates a new row element based on the given attributes
* and returns it .
*
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileData map of file attributes
2014-06-23 21:56:10 +00:00
* @ param { Object } [ options ] map of attributes
* @ param { int } [ options . index ] index at which to insert the element
* @ param { boolean } [ options . updateSummary ] true to update the summary
* after adding ( default ) , false otherwise . Defaults to true .
* @ param { boolean } [ options . animate ] true to animate the thumbnail image after load
* defaults to true .
2014-05-08 20:06:30 +00:00
* @ return new tr element ( not appended to the table )
* /
_renderRow : function ( fileData , options ) {
options = options || { } ;
var type = fileData . type || 'file' ,
mime = fileData . mimetype ,
2014-04-30 15:42:35 +00:00
path = fileData . path || this . getCurrentDirectory ( ) ,
2014-05-08 20:06:30 +00:00
permissions = parseInt ( fileData . permissions , 10 ) || 0 ;
2017-09-19 11:58:26 +00:00
var isEndToEndEncrypted = ( type === 'dir' && fileData . isEncrypted ) ;
if ( ! isEndToEndEncrypted && fileData . isShareMountPoint ) {
2014-05-08 20:06:30 +00:00
permissions = permissions | OC . PERMISSION _UPDATE ;
}
2014-04-17 13:54:45 +00:00
2014-05-08 20:06:30 +00:00
if ( type === 'dir' ) {
mime = mime || 'httpd/unix-directory' ;
}
var tr = this . _createRow (
fileData ,
options
) ;
var filenameTd = tr . find ( 'td.filename' ) ;
// TODO: move dragging to FileActions ?
// enable drag only for deletable files
2014-05-12 17:54:20 +00:00
if ( this . _dragOptions && permissions & OC . PERMISSION _DELETE ) {
filenameTd . draggable ( this . _dragOptions ) ;
2014-05-08 20:06:30 +00:00
}
// allow dropping on folders
2015-07-13 15:38:13 +00:00
if ( this . _folderDropOptions && mime === 'httpd/unix-directory' ) {
2016-03-12 03:29:59 +00:00
tr . droppable ( this . _folderDropOptions ) ;
2014-05-08 20:06:30 +00:00
}
2013-10-28 19:22:06 +00:00
2014-05-08 20:06:30 +00:00
if ( options . hidden ) {
tr . addClass ( 'hidden' ) ;
}
2013-10-28 19:22:06 +00:00
2016-08-17 15:34:15 +00:00
if ( this . _isHiddenFile ( fileData ) ) {
tr . addClass ( 'hidden-file' ) ;
}
2014-05-08 20:06:30 +00:00
// display actions
2014-05-19 13:20:44 +00:00
this . fileActions . display ( filenameTd , ! options . silent , this ) ;
2014-05-08 20:06:30 +00:00
2016-07-26 11:40:44 +00:00
if ( mime !== 'httpd/unix-directory' && fileData . hasPreview !== false ) {
2014-12-11 16:36:14 +00:00
var iconDiv = filenameTd . find ( '.thumbnail' ) ;
2014-05-08 20:06:30 +00:00
// lazy load / newly inserted td ?
2015-05-06 16:32:52 +00:00
// the typeof check ensures that the default value of animate is true
if ( typeof ( options . animate ) === 'undefined' || ! ! options . animate ) {
2014-05-08 20:06:30 +00:00
this . lazyLoadPreview ( {
2018-01-16 20:47:08 +00:00
fileId : fileData . id ,
2014-04-30 15:42:35 +00:00
path : path + '/' + fileData . name ,
2014-05-08 20:06:30 +00:00
mime : mime ,
etag : fileData . etag ,
callback : function ( url ) {
2015-02-22 15:51:16 +00:00
iconDiv . css ( 'background-image' , 'url("' + url + '")' ) ;
2014-05-08 20:06:30 +00:00
}
} ) ;
}
else {
// set the preview URL directly
var urlSpec = {
2014-04-30 15:42:35 +00:00
file : path + '/' + fileData . name ,
2014-05-08 20:06:30 +00:00
c : fileData . etag
} ;
var previewUrl = this . generatePreviewUrl ( urlSpec ) ;
2019-01-18 23:09:13 +00:00
previewUrl = previewUrl . replace ( /\(/g , '%28' ) . replace ( /\)/g , '%29' ) ;
2015-02-22 15:51:16 +00:00
iconDiv . css ( 'background-image' , 'url("' + previewUrl + '")' ) ;
2014-05-08 20:06:30 +00:00
}
}
return tr ;
} ,
/ * *
* Returns the current directory
2014-06-23 21:56:10 +00:00
* @ method getCurrentDirectory
2014-05-08 20:06:30 +00:00
* @ return current directory
* /
getCurrentDirectory : function ( ) {
return this . _currentDirectory || this . $el . find ( '#dir' ) . val ( ) || '/' ;
} ,
/ * *
* Returns the directory permissions
* @ return permission value as integer
* /
getDirectoryPermissions : function ( ) {
2019-06-18 10:34:04 +00:00
return this && this . dirInfo && this . dirInfo . permissions ? this . dirInfo . permissions : parseInt ( this . $el . find ( '#permissions' ) . val ( ) , 10 ) ;
2014-05-08 20:06:30 +00:00
} ,
/ * *
2016-05-04 09:17:53 +00:00
* Changes the current directory and reload the file list .
* @ param { string } targetDir target directory ( non URL encoded )
* @ param { boolean } [ changeUrl = true ] if the URL must not be changed ( defaults to true )
* @ param { boolean } [ force = false ] set to true to force changing directory
* @ param { string } [ fileId ] optional file id , if known , to be appended in the URL
2014-05-08 20:06:30 +00:00
* /
2016-05-04 09:17:53 +00:00
changeDirectory : function ( targetDir , changeUrl , force , fileId ) {
2014-06-30 14:27:31 +00:00
var self = this ;
2014-05-08 20:06:30 +00:00
var currentDir = this . getCurrentDirectory ( ) ;
targetDir = targetDir || '/' ;
if ( ! force && currentDir === targetDir ) {
return ;
}
2016-05-04 09:17:53 +00:00
this . _setCurrentDir ( targetDir , changeUrl , fileId ) ;
2015-12-16 16:35:53 +00:00
// discard finished uploads list, we'll get it through a regular reload
this . _uploads = { } ;
2018-08-02 12:14:17 +00:00
return this . reload ( ) . then ( function ( success ) {
2014-06-30 14:27:31 +00:00
if ( ! success ) {
self . changeDirectory ( currentDir , true ) ;
}
} ) ;
2014-05-08 20:06:30 +00:00
} ,
linkTo : function ( dir ) {
return OC . linkTo ( 'files' , 'index.php' ) + "?dir=" + encodeURIComponent ( dir ) . replace ( /%2F/g , '/' ) ;
} ,
2016-09-01 08:06:06 +00:00
/ * *
* @ param { string } path
* @ returns { boolean }
* /
2016-07-06 09:55:02 +00:00
_isValidPath : function ( path ) {
var sections = path . split ( '/' ) ;
for ( var i = 0 ; i < sections . length ; i ++ ) {
if ( sections [ i ] === '..' ) {
return false ;
}
}
2016-09-01 08:06:06 +00:00
2016-09-01 10:24:14 +00:00
return path . toLowerCase ( ) . indexOf ( decodeURI ( '%0a' ) ) === - 1 &&
path . toLowerCase ( ) . indexOf ( decodeURI ( '%00' ) ) === - 1 ;
2016-07-06 09:55:02 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Sets the current directory name and updates the breadcrumb .
* @ param targetDir directory to display
* @ param changeUrl true to also update the URL , false otherwise ( default )
2016-05-04 09:17:53 +00:00
* @ param { string } [ fileId ] file id
2014-05-08 20:06:30 +00:00
* /
2016-05-04 09:17:53 +00:00
_setCurrentDir : function ( targetDir , changeUrl , fileId ) {
2015-02-19 16:12:29 +00:00
targetDir = targetDir . replace ( /\\/g , '/' ) ;
2016-07-06 09:55:02 +00:00
if ( ! this . _isValidPath ( targetDir ) ) {
targetDir = '/' ;
changeUrl = true ;
}
2014-05-12 17:54:20 +00:00
var previousDir = this . getCurrentDirectory ( ) ,
2014-05-08 20:06:30 +00:00
baseDir = OC . basename ( targetDir ) ;
if ( baseDir !== '' ) {
this . setPageTitle ( baseDir ) ;
}
else {
this . setPageTitle ( ) ;
}
2014-04-07 21:45:35 +00:00
2016-07-01 09:10:37 +00:00
if ( targetDir . length > 0 && targetDir [ 0 ] !== '/' ) {
targetDir = '/' + targetDir ;
}
2014-05-08 20:06:30 +00:00
this . _currentDirectory = targetDir ;
2014-04-04 12:34:07 +00:00
2014-05-08 20:06:30 +00:00
// legacy stuff
this . $el . find ( '#dir' ) . val ( targetDir ) ;
2014-04-04 14:11:31 +00:00
2014-05-08 20:06:30 +00:00
if ( changeUrl !== false ) {
2016-05-04 09:17:53 +00:00
var params = {
2014-05-08 20:06:30 +00:00
dir : targetDir ,
previousDir : previousDir
2016-05-04 09:17:53 +00:00
} ;
if ( fileId ) {
params . fileId = fileId ;
}
this . $el . trigger ( jQuery . Event ( 'changeDirectory' , params ) ) ;
2014-04-04 12:34:07 +00:00
}
2014-05-08 20:06:30 +00:00
this . breadcrumb . setDirectory ( this . getCurrentDirectory ( ) ) ;
} ,
/ * *
* Sets the current sorting and refreshes the list
*
* @ param sort sort attribute name
* @ param direction sort direction , one of "asc" or "desc"
2014-08-15 14:52:41 +00:00
* @ param update true to update the list , false otherwise ( default )
2016-04-12 09:51:50 +00:00
* @ param persist true to save changes in the database ( default )
2014-05-08 20:06:30 +00:00
* /
2016-04-12 09:51:50 +00:00
setSort : function ( sort , direction , update , persist ) {
2014-05-08 20:06:30 +00:00
var comparator = FileList . Comparators [ sort ] || FileList . Comparators . name ;
this . _sort = sort ;
this . _sortDirection = ( direction === 'desc' ) ? 'desc' : 'asc' ;
2016-10-22 20:26:21 +00:00
this . _sortComparator = function ( fileInfo1 , fileInfo2 ) {
Fix sorting of favorite files
The sort comparator checks the "isFavorite" property of the FileInfo
objects to compare. That property is set when the file list is loaded
and the response from the server is parsed, and thus a freshly loaded
file list has the proper sorting for favorite files. However, the
property is not set in other cases, like when the FileInfo objects are
derived from FileInfoModels due to a file being marked as a favorite or
a text editor being closed, which causes the file to be sorted in the
wrong position.
There is no need to add the property in those situations, though; in all
cases the TagsPlugin adds a "tags" array property that contains an
OC.TAG_FAVORITE tag, so that tag can be checked instead of "isFavorite".
Moreover, although "isFavorite" was added by the main "_parseFileInfo"
function it did not really belong there but to the "FileInfoParser" from
the TagsPlugin; however, as that property now is not used anywhere it
was removed altogether.
A cleaner solution would have been to make the sort comparator
extensible by plugins like other behaviours of the file list and then
add the sorting logic related to favorite files to the TagsPlugin.
However, right now only the TagsPlugin would need to alter the main
sorting logic, and it seems like a corner case anyway. Even if it is
implemented as a plugin, favorite files is a core feature, so for the
time being it will be taken into account directly in the main sorting
logic; making the sort comparator extensible by plugins is defered until
there are other use cases for that.
Fixes #5410
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2017-07-05 13:01:23 +00:00
var isFavorite = function ( fileInfo ) {
return fileInfo . tags && fileInfo . tags . indexOf ( OC . TAG _FAVORITE ) >= 0 ;
} ;
if ( isFavorite ( fileInfo1 ) && ! isFavorite ( fileInfo2 ) ) {
2016-10-22 20:26:21 +00:00
return - 1 ;
Fix sorting of favorite files
The sort comparator checks the "isFavorite" property of the FileInfo
objects to compare. That property is set when the file list is loaded
and the response from the server is parsed, and thus a freshly loaded
file list has the proper sorting for favorite files. However, the
property is not set in other cases, like when the FileInfo objects are
derived from FileInfoModels due to a file being marked as a favorite or
a text editor being closed, which causes the file to be sorted in the
wrong position.
There is no need to add the property in those situations, though; in all
cases the TagsPlugin adds a "tags" array property that contains an
OC.TAG_FAVORITE tag, so that tag can be checked instead of "isFavorite".
Moreover, although "isFavorite" was added by the main "_parseFileInfo"
function it did not really belong there but to the "FileInfoParser" from
the TagsPlugin; however, as that property now is not used anywhere it
was removed altogether.
A cleaner solution would have been to make the sort comparator
extensible by plugins like other behaviours of the file list and then
add the sorting logic related to favorite files to the TagsPlugin.
However, right now only the TagsPlugin would need to alter the main
sorting logic, and it seems like a corner case anyway. Even if it is
implemented as a plugin, favorite files is a core feature, so for the
time being it will be taken into account directly in the main sorting
logic; making the sort comparator extensible by plugins is defered until
there are other use cases for that.
Fixes #5410
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2017-07-05 13:01:23 +00:00
} else if ( ! isFavorite ( fileInfo1 ) && isFavorite ( fileInfo2 ) ) {
2016-10-22 20:26:21 +00:00
return 1 ;
}
Fix sorting of favorite files
The sort comparator checks the "isFavorite" property of the FileInfo
objects to compare. That property is set when the file list is loaded
and the response from the server is parsed, and thus a freshly loaded
file list has the proper sorting for favorite files. However, the
property is not set in other cases, like when the FileInfo objects are
derived from FileInfoModels due to a file being marked as a favorite or
a text editor being closed, which causes the file to be sorted in the
wrong position.
There is no need to add the property in those situations, though; in all
cases the TagsPlugin adds a "tags" array property that contains an
OC.TAG_FAVORITE tag, so that tag can be checked instead of "isFavorite".
Moreover, although "isFavorite" was added by the main "_parseFileInfo"
function it did not really belong there but to the "FileInfoParser" from
the TagsPlugin; however, as that property now is not used anywhere it
was removed altogether.
A cleaner solution would have been to make the sort comparator
extensible by plugins like other behaviours of the file list and then
add the sorting logic related to favorite files to the TagsPlugin.
However, right now only the TagsPlugin would need to alter the main
sorting logic, and it seems like a corner case anyway. Even if it is
implemented as a plugin, favorite files is a core feature, so for the
time being it will be taken into account directly in the main sorting
logic; making the sort comparator extensible by plugins is defered until
there are other use cases for that.
Fixes #5410
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
2017-07-05 13:01:23 +00:00
2016-10-22 20:26:21 +00:00
return direction === 'asc' ? comparator ( fileInfo1 , fileInfo2 ) : - comparator ( fileInfo1 , fileInfo2 ) ;
} ;
2014-07-17 10:48:50 +00:00
2014-05-08 20:06:30 +00:00
this . $el . find ( 'thead th .sort-indicator' )
2014-07-17 10:48:50 +00:00
. removeClass ( this . SORT _INDICATOR _ASC _CLASS )
. removeClass ( this . SORT _INDICATOR _DESC _CLASS )
. toggleClass ( 'hidden' , true )
. addClass ( this . SORT _INDICATOR _DESC _CLASS ) ;
2014-05-08 20:06:30 +00:00
this . $el . find ( 'thead th.column-' + sort + ' .sort-indicator' )
2014-07-17 10:48:50 +00:00
. removeClass ( this . SORT _INDICATOR _ASC _CLASS )
. removeClass ( this . SORT _INDICATOR _DESC _CLASS )
. toggleClass ( 'hidden' , false )
2014-05-08 20:06:30 +00:00
. addClass ( direction === 'desc' ? this . SORT _INDICATOR _DESC _CLASS : this . SORT _INDICATOR _ASC _CLASS ) ;
2014-08-15 14:52:41 +00:00
if ( update ) {
if ( this . _clientSideSort ) {
this . files . sort ( this . _sortComparator ) ;
this . setFiles ( this . files ) ;
}
else {
this . reload ( ) ;
}
}
2016-04-12 09:08:26 +00:00
2018-08-17 18:09:11 +00:00
if ( persist && OC . getCurrentUser ( ) . uid ) {
2016-04-12 09:51:50 +00:00
$ . post ( OC . generateUrl ( '/apps/files/api/v1/sorting' ) , {
mode : sort ,
direction : direction
} ) ;
}
2014-05-08 20:06:30 +00:00
} ,
2014-07-17 10:48:50 +00:00
2015-11-02 13:17:49 +00:00
/ * *
* Returns list of webdav properties to request
* /
_getWebdavProperties : function ( ) {
2015-12-22 10:17:24 +00:00
return [ ] . concat ( this . filesClient . getPropfindProperties ( ) ) ;
2015-11-02 13:17:49 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
2014-07-04 09:45:36 +00:00
* Reloads the file list using ajax call
*
* @ return ajax call object
2014-05-08 20:06:30 +00:00
* /
reload : function ( ) {
this . _selectedFiles = { } ;
this . _selectionSummary . clear ( ) ;
2015-09-01 17:29:55 +00:00
if ( this . _currentFileModel ) {
this . _currentFileModel . off ( ) ;
}
this . _currentFileModel = null ;
2014-05-12 17:54:20 +00:00
this . $el . find ( '.select-all' ) . prop ( 'checked' , false ) ;
2014-05-08 20:06:30 +00:00
this . showMask ( ) ;
2015-11-02 13:17:49 +00:00
this . _reloadCall = this . filesClient . getFolderContents (
this . getCurrentDirectory ( ) , {
includeParent : true ,
properties : this . _getWebdavProperties ( )
}
) ;
2015-09-25 10:23:28 +00:00
if ( this . _detailsView ) {
// close sidebar
this . _updateDetailsView ( null ) ;
}
2017-10-18 17:51:40 +00:00
this . _setCurrentDir ( this . getCurrentDirectory ( ) , false ) ;
2014-06-30 14:27:31 +00:00
var callBack = this . reloadCallback . bind ( this ) ;
return this . _reloadCall . then ( callBack , callBack ) ;
2014-05-08 20:06:30 +00:00
} ,
2015-07-13 15:38:13 +00:00
reloadCallback : function ( status , result ) {
2014-05-08 20:06:30 +00:00
delete this . _reloadCall ;
this . hideMask ( ) ;
2016-03-02 16:42:51 +00:00
if ( status === 401 ) {
return false ;
}
2015-06-19 14:31:22 +00:00
// Firewall Blocked request?
2015-07-13 15:38:13 +00:00
if ( status === 403 ) {
2015-06-19 14:31:22 +00:00
// Go home
this . changeDirectory ( '/' ) ;
2017-02-14 20:26:00 +00:00
OC . Notification . show ( t ( 'files' , 'This operation is forbidden' ) , { type : 'error' } ) ;
2015-06-19 14:31:22 +00:00
return false ;
}
2015-06-26 15:19:59 +00:00
// Did share service die or something else fail?
2015-07-13 15:38:13 +00:00
if ( status === 500 ) {
2015-06-26 15:19:59 +00:00
// Go home
this . changeDirectory ( '/' ) ;
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'This directory is unavailable, please check the logs or contact the administrator' ) ,
2017-02-14 20:26:00 +00:00
{ type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
return false ;
}
if ( status === 503 ) {
// Go home
if ( this . getCurrentDirectory ( ) !== '/' ) {
this . changeDirectory ( '/' ) ;
// TODO: read error message from exception
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Storage is temporarily not available' ) ,
2017-02-14 20:26:00 +00:00
{ type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
}
2015-06-26 15:19:59 +00:00
return false ;
}
2016-12-01 18:13:28 +00:00
if ( status === 400 || status === 404 || status === 405 ) {
2014-05-08 20:06:30 +00:00
// go back home
this . changeDirectory ( '/' ) ;
2014-06-30 14:27:31 +00:00
return false ;
2014-05-08 20:06:30 +00:00
}
// aborted ?
2015-07-13 15:38:13 +00:00
if ( status === 0 ) {
2014-06-30 14:27:31 +00:00
return true ;
2014-05-08 20:06:30 +00:00
}
2013-02-22 16:21:57 +00:00
2014-05-12 17:54:20 +00:00
this . updateStorageStatistics ( true ) ;
2013-10-28 19:22:06 +00:00
2015-07-13 15:38:13 +00:00
// first entry is the root
this . dirInfo = result . shift ( ) ;
2016-10-04 10:56:04 +00:00
this . breadcrumb . setDirectoryInfo ( this . dirInfo ) ;
2015-07-13 15:38:13 +00:00
if ( this . dirInfo . permissions ) {
2019-05-28 08:27:01 +00:00
this . _updateDirectoryPermissions ( ) ;
2014-05-08 20:06:30 +00:00
}
2013-10-28 19:22:06 +00:00
2015-07-13 15:38:13 +00:00
result . sort ( this . _sortComparator ) ;
this . setFiles ( result ) ;
2016-05-04 09:17:53 +00:00
if ( this . dirInfo ) {
var newFileId = this . dirInfo . id ;
// update fileid in URL
var params = {
dir : this . getCurrentDirectory ( )
} ;
if ( newFileId ) {
params . fileId = newFileId ;
}
this . $el . trigger ( jQuery . Event ( 'afterChangeDirectory' , params ) ) ;
}
2014-07-04 12:08:48 +00:00
return true ;
2014-05-08 20:06:30 +00:00
} ,
2014-05-12 17:54:20 +00:00
updateStorageStatistics : function ( force ) {
OCA . Files . Files . updateStorageStatistics ( this . getCurrentDirectory ( ) , force ) ;
} ,
2018-01-03 12:59:36 +00:00
updateStorageQuotas : function ( ) {
2018-01-03 16:53:48 +00:00
OCA . Files . Files . updateStorageQuotas ( ) ;
2018-01-03 12:59:36 +00:00
} ,
2015-07-13 15:38:13 +00:00
/ * *
* @ deprecated do not use nor override
* /
2014-05-08 20:06:30 +00:00
getAjaxUrl : function ( action , params ) {
2014-05-12 17:54:20 +00:00
return OCA . Files . Files . getAjaxUrl ( action , params ) ;
2014-05-08 20:06:30 +00:00
} ,
2015-07-13 15:38:13 +00:00
getDownloadUrl : function ( files , dir , isDir ) {
return OCA . Files . Files . getDownloadUrl ( files , dir || this . getCurrentDirectory ( ) , isDir ) ;
2014-05-08 20:06:30 +00:00
} ,
2015-12-16 16:35:53 +00:00
getUploadUrl : function ( fileName , dir ) {
if ( _ . isUndefined ( dir ) ) {
dir = this . getCurrentDirectory ( ) ;
}
var pathSections = dir . split ( '/' ) ;
if ( ! _ . isUndefined ( fileName ) ) {
pathSections . push ( fileName ) ;
}
var encodedPath = '' ;
_ . each ( pathSections , function ( section ) {
if ( section !== '' ) {
encodedPath += '/' + encodeURIComponent ( section ) ;
}
} ) ;
return OC . linkToRemoteBase ( 'webdav' ) + encodedPath ;
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Generates a preview URL based on the URL space .
2014-06-23 21:56:10 +00:00
* @ param urlSpec attributes for the URL
* @ param { int } urlSpec . x width
* @ param { int } urlSpec . y height
* @ param { String } urlSpec . file path to the file
2014-05-08 20:06:30 +00:00
* @ return preview URL
* /
generatePreviewUrl : function ( urlSpec ) {
urlSpec = urlSpec || { } ;
if ( ! urlSpec . x ) {
2018-11-06 10:45:19 +00:00
urlSpec . x = this . $table . data ( 'preview-x' ) || 250 ;
2013-10-28 19:22:06 +00:00
}
2014-05-08 20:06:30 +00:00
if ( ! urlSpec . y ) {
2018-11-06 10:45:19 +00:00
urlSpec . y = this . $table . data ( 'preview-y' ) || 250 ;
2013-10-28 19:22:06 +00:00
}
2014-05-08 20:06:30 +00:00
urlSpec . x *= window . devicePixelRatio ;
2015-06-11 15:17:40 +00:00
urlSpec . y *= window . devicePixelRatio ;
2015-09-27 10:46:25 +00:00
urlSpec . x = Math . ceil ( urlSpec . x ) ;
urlSpec . y = Math . ceil ( urlSpec . y ) ;
2014-05-08 20:06:30 +00:00
urlSpec . forceIcon = 0 ;
2018-01-16 20:47:08 +00:00
if ( typeof urlSpec . fileId !== 'undefined' ) {
delete urlSpec . file ;
return OC . generateUrl ( '/core/preview?' ) + $ . param ( urlSpec ) ;
} else {
delete urlSpec . fileId ;
return OC . generateUrl ( '/core/preview.png?' ) + $ . param ( urlSpec ) ;
}
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Lazy load a file ' s preview .
2014-05-26 18:32:24 +00:00
*
2014-05-08 20:06:30 +00:00
* @ param path path of the file
* @ param mime mime type
* @ param callback callback function to call when the image was loaded
* @ param etag file etag ( for caching )
* /
lazyLoadPreview : function ( options ) {
var self = this ;
2018-01-16 20:47:08 +00:00
var fileId = options . fileId ;
2014-05-08 20:06:30 +00:00
var path = options . path ;
var mime = options . mime ;
var ready = options . callback ;
var etag = options . etag ;
// get mime icon url
2015-07-08 12:55:11 +00:00
var iconURL = OC . MimeType . getIconUrl ( mime ) ;
var previewURL ,
urlSpec = { } ;
ready ( iconURL ) ; // set mimeicon URL
2014-05-08 20:06:30 +00:00
2018-01-16 20:47:08 +00:00
urlSpec . fileId = fileId ;
2015-07-08 12:55:11 +00:00
urlSpec . file = OCA . Files . Files . fixPath ( path ) ;
2015-07-16 10:49:34 +00:00
if ( options . x ) {
urlSpec . x = options . x ;
}
if ( options . y ) {
urlSpec . y = options . y ;
}
2015-08-28 15:51:26 +00:00
if ( options . a ) {
urlSpec . a = options . a ;
}
if ( options . mode ) {
urlSpec . mode = options . mode ;
}
2014-05-08 20:06:30 +00:00
2015-07-08 12:55:11 +00:00
if ( etag ) {
// use etag as cache buster
urlSpec . c = etag ;
}
2014-02-20 14:16:45 +00:00
2015-07-08 12:55:11 +00:00
previewURL = self . generatePreviewUrl ( urlSpec ) ;
2019-01-25 17:36:45 +00:00
previewURL = previewURL . replace ( /\(/g , '%28' ) . replace ( /\)/g , '%29' ) ;
2015-07-08 12:55:11 +00:00
// preload image to prevent delay
// this will make the browser cache the image
var img = new Image ( ) ;
img . onload = function ( ) {
// if loading the preview image failed (no preview for the mimetype) then img.width will < 5
if ( img . width > 5 ) {
2015-08-28 15:51:26 +00:00
ready ( previewURL , img ) ;
} else if ( options . error ) {
options . error ( ) ;
2015-07-08 12:55:11 +00:00
}
} ;
2015-08-28 15:51:26 +00:00
if ( options . error ) {
img . onerror = options . error ;
}
2015-07-08 12:55:11 +00:00
img . src = previewURL ;
2014-05-08 20:06:30 +00:00
} ,
2014-02-20 14:16:45 +00:00
2019-05-28 08:27:01 +00:00
_updateDirectoryPermissions : function ( ) {
var isCreatable = ( this . dirInfo . permissions & OC . PERMISSION _CREATE ) !== 0 && this . $el . find ( '#free_space' ) . val ( ) !== '0' ;
2019-07-01 09:58:13 +00:00
this . $el . find ( '#permissions' ) . val ( this . dirInfo . permissions ) ;
2014-05-08 20:06:30 +00:00
this . $el . find ( '.creatable' ) . toggleClass ( 'hidden' , ! isCreatable ) ;
this . $el . find ( '.notCreatable' ) . toggleClass ( 'hidden' , isCreatable ) ;
} ,
/ * *
* Shows / hides action buttons
*
* @ param show true for enabling , false for disabling
* /
showActions : function ( show ) {
this . $el . find ( '.actions,#file_action_panel' ) . toggleClass ( 'hidden' , ! show ) ;
if ( show ) {
// make sure to display according to permissions
var permissions = this . getDirectoryPermissions ( ) ;
var isCreatable = ( permissions & OC . PERMISSION _CREATE ) !== 0 ;
this . $el . find ( '.creatable' ) . toggleClass ( 'hidden' , ! isCreatable ) ;
this . $el . find ( '.notCreatable' ) . toggleClass ( 'hidden' , isCreatable ) ;
// remove old style breadcrumbs (some apps might create them)
this . $el . find ( '#controls .crumb' ) . remove ( ) ;
// refresh breadcrumbs in case it was replaced by an app
this . breadcrumb . render ( ) ;
2013-08-29 19:56:14 +00:00
}
else {
2014-05-08 20:06:30 +00:00
this . $el . find ( '.creatable, .notCreatable' ) . addClass ( 'hidden' ) ;
}
} ,
/ * *
* Enables / disables viewer mode .
* In viewer mode , apps can embed themselves under the controls bar .
* In viewer mode , the actions of the file list will be hidden .
* @ param show true for enabling , false for disabling
* /
setViewerMode : function ( show ) {
this . showActions ( ! show ) ;
this . $el . find ( '#filestable' ) . toggleClass ( 'hidden' , show ) ;
2014-05-12 17:54:20 +00:00
this . $el . trigger ( new $ . Event ( 'changeViewerMode' , { viewerModeEnabled : show } ) ) ;
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Removes a file entry from the list
* @ param name name of the file to remove
2014-06-23 21:56:10 +00:00
* @ param { Object } [ options ] map of attributes
* @ param { boolean } [ options . updateSummary ] true to update the summary
* after removing , false otherwise . Defaults to true .
2014-05-08 20:06:30 +00:00
* @ return deleted element
* /
remove : function ( name , options ) {
options = options || { } ;
var fileEl = this . findFileEl ( name ) ;
2018-11-19 21:52:19 +00:00
var fileData = _ . findWhere ( this . files , { name : name } ) ;
2018-11-22 21:19:57 +00:00
if ( ! fileData ) {
return ;
2014-05-08 20:06:30 +00:00
}
2018-11-19 21:52:19 +00:00
var fileId = fileData . id ;
2015-09-25 10:23:28 +00:00
if ( this . _selectedFiles [ fileId ] ) {
2014-05-08 20:06:30 +00:00
// remove from selection first
this . _selectFileEl ( fileEl , false ) ;
this . updateSelectionSummary ( ) ;
}
2018-11-19 21:52:19 +00:00
if ( this . _selectedFiles [ fileId ] ) {
delete this . _selectedFiles [ fileId ] ;
this . _selectionSummary . remove ( fileData ) ;
this . updateSelectionSummary ( ) ;
}
var index = this . files . findIndex ( function ( el ) { return el . name == name ; } ) ;
this . files . splice ( index , 1 ) ;
// TODO: improve performance on batch update
this . isEmpty = ! this . files . length ;
if ( typeof ( options . updateSummary ) === 'undefined' || ! ! options . updateSummary ) {
this . updateEmptyContent ( ) ;
this . fileSummary . remove ( { type : fileData . type , size : fileData . size } , true ) ;
}
if ( ! fileEl . length ) {
return null ;
}
2014-05-12 17:54:20 +00:00
if ( this . _dragOptions && ( fileEl . data ( 'permissions' ) & OC . PERMISSION _DELETE ) ) {
2014-05-08 20:06:30 +00:00
// file is only draggable when delete permissions are set
fileEl . find ( 'td.filename' ) . draggable ( 'destroy' ) ;
}
2015-09-25 10:23:28 +00:00
if ( this . _currentFileModel && this . _currentFileModel . get ( 'id' ) === fileId ) {
// Note: in the future we should call destroy() directly on the model
// and the model will take care of the deletion.
// Here we only trigger the event to notify listeners that
// the file was removed.
this . _currentFileModel . trigger ( 'destroy' ) ;
this . _updateDetailsView ( null ) ;
}
2014-05-08 20:06:30 +00:00
fileEl . remove ( ) ;
2013-08-17 11:07:18 +00:00
2014-05-08 20:06:30 +00:00
var lastIndex = this . $fileList . children ( ) . length ;
// if there are less elements visible than one page
// but there are still pending elements in the array,
// then directly append the next page
2014-10-11 13:10:54 +00:00
if ( lastIndex < this . files . length && lastIndex < this . pageSize ( ) ) {
2014-05-08 20:06:30 +00:00
this . _nextPage ( true ) ;
}
2013-08-17 11:07:18 +00:00
2014-05-08 20:06:30 +00:00
return fileEl ;
} ,
/ * *
* Finds the index of the row before which the given
* fileData should be inserted , considering the current
* sorting
2014-06-23 21:56:10 +00:00
*
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileData file info
2014-05-08 20:06:30 +00:00
* /
_findInsertionIndex : function ( fileData ) {
var index = 0 ;
while ( index < this . files . length && this . _sortComparator ( fileData , this . files [ index ] ) > 0 ) {
index ++ ;
}
return index ;
} ,
2017-08-27 13:28:26 +00:00
2014-05-08 20:06:30 +00:00
/ * *
* Moves a file to a given target folder .
*
* @ param fileNames array of file names to move
* @ param targetPath absolute target path
2017-08-27 13:20:18 +00:00
* @ param callback function to call when movement is finished
2018-03-22 14:31:18 +00:00
* @ param dir the dir path where fileNames are located ( optionnal , will take current folder if undefined )
2014-05-08 20:06:30 +00:00
* /
2018-03-22 14:31:18 +00:00
move : function ( fileNames , targetPath , callback , dir ) {
2014-05-08 20:06:30 +00:00
var self = this ;
2018-03-22 14:31:18 +00:00
dir = typeof dir === 'string' ? dir : this . getCurrentDirectory ( ) ;
2015-07-13 15:38:13 +00:00
if ( dir . charAt ( dir . length - 1 ) !== '/' ) {
dir += '/' ;
}
2014-05-08 20:06:30 +00:00
var target = OC . basename ( targetPath ) ;
if ( ! _ . isArray ( fileNames ) ) {
fileNames = [ fileNames ] ;
}
2018-10-29 20:58:43 +00:00
var moveFileFunction = function ( fileName ) {
2014-05-08 20:06:30 +00:00
var $tr = self . findFileEl ( fileName ) ;
2015-07-16 13:28:45 +00:00
self . showFileBusyState ( $tr , true ) ;
2015-07-13 15:38:13 +00:00
if ( targetPath . charAt ( targetPath . length - 1 ) !== '/' ) {
// make sure we move the files into the target dir,
// not overwrite it
targetPath = targetPath + '/' ;
}
2018-10-29 20:58:43 +00:00
return self . filesClient . move ( dir + fileName , targetPath + fileName )
2015-07-13 15:38:13 +00:00
. done ( function ( ) {
// if still viewing the same directory
2018-11-22 21:19:57 +00:00
if ( OC . joinPaths ( self . getCurrentDirectory ( ) , '/' ) === OC . joinPaths ( dir , '/' ) ) {
2015-07-13 15:38:13 +00:00
// recalculate folder size
var oldFile = self . findFileEl ( target ) ;
var newFile = self . findFileEl ( fileName ) ;
var oldSize = oldFile . data ( 'size' ) ;
var newSize = oldSize + newFile . data ( 'size' ) ;
oldFile . data ( 'size' , newSize ) ;
oldFile . find ( 'td.filesize' ) . text ( OC . Util . humanFileSize ( newSize ) ) ;
self . remove ( fileName ) ;
}
} )
. fail ( function ( status ) {
if ( status === 412 ) {
// TODO: some day here we should invoke the conflict dialog
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not move "{file}", target exists' ,
2017-02-14 20:26:00 +00:00
{ file : fileName } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
2014-05-08 20:06:30 +00:00
} else {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not move "{file}"' ,
2017-02-14 20:26:00 +00:00
{ file : fileName } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
2014-05-08 20:06:30 +00:00
}
2015-07-13 15:38:13 +00:00
} )
. always ( function ( ) {
2015-07-16 13:28:45 +00:00
self . showFileBusyState ( $tr , false ) ;
2015-07-13 15:38:13 +00:00
} ) ;
2018-10-29 20:58:43 +00:00
} ;
2018-11-14 21:39:12 +00:00
return this . reportOperationProgress ( fileNames , moveFileFunction , callback ) ;
} ,
2018-10-29 20:58:43 +00:00
2018-11-15 16:51:03 +00:00
_reflect : function ( promise ) {
2018-11-19 21:52:19 +00:00
return promise . then ( function ( v ) { return { } ; } , function ( e ) { return { } ; } ) ;
2018-11-15 16:51:03 +00:00
} ,
2018-10-29 20:58:43 +00:00
2018-11-14 21:39:12 +00:00
reportOperationProgress : function ( fileNames , operationFunction , callback ) {
var self = this ;
self . _operationProgressBar . showProgressBar ( false ) ;
var mcSemaphore = new OCA . Files . Semaphore ( 5 ) ;
2018-10-29 20:58:43 +00:00
var counter = 0 ;
2018-11-03 23:01:32 +00:00
var promises = _ . map ( fileNames , function ( arg ) {
return mcSemaphore . acquire ( ) . then ( function ( ) {
2018-11-15 16:51:03 +00:00
return operationFunction ( arg ) . always ( function ( ) {
2018-10-29 20:58:43 +00:00
mcSemaphore . release ( ) ;
counter ++ ;
2018-11-14 21:39:12 +00:00
self . _operationProgressBar . setProgressBarValue ( 100.0 * counter / fileNames . length ) ;
2018-10-29 20:58:43 +00:00
} ) ;
} ) ;
2014-05-08 20:06:30 +00:00
} ) ;
2013-11-06 09:55:19 +00:00
2018-11-15 16:51:03 +00:00
return Promise . all ( _ . map ( promises , self . _reflect ) ) . then ( function ( ) {
2017-08-27 13:20:18 +00:00
if ( callback ) {
callback ( ) ;
}
2018-11-14 21:39:12 +00:00
self . _operationProgressBar . hideProgressBar ( ) ;
2014-05-08 20:06:30 +00:00
} ) ;
} ,
2017-08-27 13:28:26 +00:00
/ * *
* Copies a file to a given target folder .
*
* @ param fileNames array of file names to copy
* @ param targetPath absolute target path
* @ param callback to call when copy is finished with success
2018-03-22 14:31:18 +00:00
* @ param dir the dir path where fileNames are located ( optionnal , will take current folder if undefined )
2017-08-27 13:28:26 +00:00
* /
2018-03-22 14:31:18 +00:00
copy : function ( fileNames , targetPath , callback , dir ) {
2017-08-27 13:28:26 +00:00
var self = this ;
2017-09-15 16:13:45 +00:00
var filesToNotify = [ ] ;
var count = 0 ;
2018-03-22 14:31:18 +00:00
dir = typeof dir === 'string' ? dir : this . getCurrentDirectory ( ) ;
2017-08-27 13:28:26 +00:00
if ( dir . charAt ( dir . length - 1 ) !== '/' ) {
dir += '/' ;
}
2017-09-15 16:13:45 +00:00
var target = OC . basename ( targetPath ) ;
2017-08-27 13:28:26 +00:00
if ( ! _ . isArray ( fileNames ) ) {
fileNames = [ fileNames ] ;
}
2018-11-14 21:39:12 +00:00
var copyFileFunction = function ( fileName ) {
2017-08-27 13:28:26 +00:00
var $tr = self . findFileEl ( fileName ) ;
self . showFileBusyState ( $tr , true ) ;
if ( targetPath . charAt ( targetPath . length - 1 ) !== '/' ) {
// make sure we move the files into the target dir,
// not overwrite it
targetPath = targetPath + '/' ;
}
2018-11-19 14:22:10 +00:00
var targetPathAndName = targetPath + fileName ;
2018-08-23 17:23:41 +00:00
if ( ( dir + fileName ) === targetPathAndName ) {
2018-11-19 14:22:10 +00:00
var dotIndex = targetPathAndName . indexOf ( "." ) ;
2018-08-23 17:23:41 +00:00
if ( dotIndex > 1 ) {
2018-11-19 14:22:10 +00:00
var leftPartOfName = targetPathAndName . substr ( 0 , dotIndex ) ;
var fileNumber = leftPartOfName . match ( /\d+/ ) ;
2018-11-15 13:20:47 +00:00
// TRANSLATORS name that is appended to copied files with the same name, will be put in parenthesis and appened with a number if it is the second+ copy
2018-11-19 14:22:10 +00:00
var copyNameLocalized = t ( 'files' , 'copy' ) ;
2018-08-23 17:23:41 +00:00
if ( isNaN ( fileNumber ) ) {
fileNumber ++ ;
2018-11-15 13:20:47 +00:00
targetPathAndName = targetPathAndName . replace ( /(?=\.[^.]+$)/g , " (" + copyNameLocalized + " " + fileNumber + ")" ) ;
2018-08-23 17:23:41 +00:00
}
else {
2018-10-08 18:11:29 +00:00
// Check if we have other files with 'copy X' and the same name
2018-11-19 14:22:10 +00:00
var maxNum = 1 ;
2018-08-23 17:23:41 +00:00
if ( self . files !== null ) {
leftPartOfName = leftPartOfName . replace ( "/" , "" ) ;
2018-11-15 13:20:47 +00:00
leftPartOfName = leftPartOfName . replace ( new RegExp ( "\\(" + copyNameLocalized + "( \\d+)?\\)" ) , "" ) ;
// find the last file with the number extension and add one to the new name
2018-11-19 14:22:10 +00:00
for ( var j = 0 ; j < self . files . length ; j ++ ) {
var cName = self . files [ j ] . name ;
2018-08-23 17:23:41 +00:00
if ( cName . indexOf ( leftPartOfName ) > - 1 ) {
2018-11-15 13:20:47 +00:00
if ( cName . indexOf ( "(" + copyNameLocalized + ")" ) > 0 ) {
targetPathAndName = targetPathAndName . replace ( new RegExp ( " \\(" + copyNameLocalized + "\\)" ) , "" ) ;
2018-10-08 18:11:29 +00:00
if ( maxNum == 1 ) {
maxNum = 2 ;
}
}
else {
2018-11-19 14:22:10 +00:00
var cFileNumber = cName . match ( new RegExp ( "\\(" + copyNameLocalized + " (\\d+)\\)" ) ) ;
2018-10-08 18:11:29 +00:00
if ( cFileNumber && parseInt ( cFileNumber [ 1 ] ) >= maxNum ) {
maxNum = parseInt ( cFileNumber [ 1 ] ) + 1 ;
}
2018-08-23 17:23:41 +00:00
}
}
}
2018-11-15 13:20:47 +00:00
targetPathAndName = targetPathAndName . replace ( new RegExp ( " \\(" + copyNameLocalized + " \\d+\\)" ) , "" ) ;
2018-08-23 17:23:41 +00:00
}
// Create the new file name with _x at the end
2018-10-08 18:11:29 +00:00
// Start from 2 per a special request of the 'standard'
2018-11-19 14:22:10 +00:00
var extensionName = " (" + copyNameLocalized + " " + maxNum + ")" ;
2018-10-08 18:11:29 +00:00
if ( maxNum == 1 ) {
2018-11-15 13:20:47 +00:00
extensionName = " (" + copyNameLocalized + ")" ;
2018-10-08 18:11:29 +00:00
}
2018-11-15 13:20:47 +00:00
targetPathAndName = targetPathAndName . replace ( /(?=\.[^.]+$)/g , extensionName ) ;
2018-08-23 17:23:41 +00:00
}
}
}
2018-11-19 21:52:19 +00:00
return self . filesClient . copy ( dir + fileName , targetPathAndName )
2017-09-15 16:13:45 +00:00
. done ( function ( ) {
filesToNotify . push ( fileName ) ;
// if still viewing the same directory
2018-11-22 21:19:57 +00:00
if ( OC . joinPaths ( self . getCurrentDirectory ( ) , '/' ) === OC . joinPaths ( dir , '/' ) ) {
2017-09-15 16:13:45 +00:00
// recalculate folder size
var oldFile = self . findFileEl ( target ) ;
var newFile = self . findFileEl ( fileName ) ;
var oldSize = oldFile . data ( 'size' ) ;
var newSize = oldSize + newFile . data ( 'size' ) ;
oldFile . data ( 'size' , newSize ) ;
oldFile . find ( 'td.filesize' ) . text ( OC . Util . humanFileSize ( newSize ) ) ;
}
2018-08-23 17:23:41 +00:00
self . reload ( ) ;
2017-09-15 16:13:45 +00:00
} )
2017-08-27 13:28:26 +00:00
. fail ( function ( status ) {
if ( status === 412 ) {
// TODO: some day here we should invoke the conflict dialog
OC . Notification . show ( t ( 'files' , 'Could not copy "{file}", target exists' ,
{ file : fileName } ) , { type : 'error' }
) ;
} else {
OC . Notification . show ( t ( 'files' , 'Could not copy "{file}"' ,
{ file : fileName } ) , { type : 'error' }
) ;
}
} )
. always ( function ( ) {
self . showFileBusyState ( $tr , false ) ;
2017-09-15 16:13:45 +00:00
count ++ ;
/ * *
* We only show the notifications once the last file has been copied
* /
if ( count === fileNames . length ) {
// Remove leading and ending /
if ( targetPath . slice ( 0 , 1 ) === '/' ) {
targetPath = targetPath . slice ( 1 , targetPath . length ) ;
}
if ( targetPath . slice ( - 1 ) === '/' ) {
targetPath = targetPath . slice ( 0 , - 1 ) ;
}
if ( filesToNotify . length > 0 ) {
// Since there's no visual indication that the files were copied, let's send some notifications !
if ( filesToNotify . length === 1 ) {
OC . Notification . show ( t ( 'files' , 'Copied {origin} inside {destination}' ,
{
origin : filesToNotify [ 0 ] ,
destination : targetPath
}
) , { timeout : 10 } ) ;
} else if ( filesToNotify . length > 0 && filesToNotify . length < 3 ) {
OC . Notification . show ( t ( 'files' , 'Copied {origin} inside {destination}' ,
{
origin : filesToNotify . join ( ', ' ) ,
destination : targetPath
}
) , { timeout : 10 } ) ;
} else {
OC . Notification . show ( t ( 'files' , 'Copied {origin} and {nbfiles} other files inside {destination}' ,
{
origin : filesToNotify [ 0 ] ,
nbfiles : filesToNotify . length - 1 ,
destination : targetPath
}
) , { timeout : 10 } ) ;
}
}
}
2017-08-27 13:28:26 +00:00
} ) ;
2018-11-14 21:39:12 +00:00
} ;
return this . reportOperationProgress ( fileNames , copyFileFunction , callback ) ;
2017-08-27 13:28:26 +00:00
} ,
2015-09-01 17:29:55 +00:00
/ * *
* Updates the given row with the given file info
*
* @ param { Object } $tr row element
* @ param { OCA . Files . FileInfo } fileInfo file info
* @ param { Object } options options
*
* @ return { Object } new row element
* /
updateRow : function ( $tr , fileInfo , options ) {
this . files . splice ( $tr . index ( ) , 1 ) ;
$tr . remove ( ) ;
2016-01-09 21:07:34 +00:00
options = _ . extend ( { silent : true } , options ) ;
options = _ . extend ( options , { updateSummary : false } ) ;
$tr = this . add ( fileInfo , options ) ;
2015-09-01 17:29:55 +00:00
this . $fileList . trigger ( $ . Event ( 'fileActionsReady' , { fileList : this , $files : $tr } ) ) ;
return $tr ;
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Triggers file rename input field for the given file name .
* If the user enters a new name , the file will be renamed .
*
2015-07-13 15:38:13 +00:00
* @ param oldName file name of the file to rename
2014-05-08 20:06:30 +00:00
* /
2015-07-13 15:38:13 +00:00
rename : function ( oldName ) {
2014-05-08 20:06:30 +00:00
var self = this ;
var tr , td , input , form ;
2015-07-13 15:38:13 +00:00
tr = this . findFileEl ( oldName ) ;
2014-05-08 20:06:30 +00:00
var oldFileInfo = this . files [ tr . index ( ) ] ;
tr . data ( 'renaming' , true ) ;
td = tr . children ( 'td.filename' ) ;
2015-07-13 15:38:13 +00:00
input = $ ( '<input type="text" class="filename"/>' ) . val ( oldName ) ;
2014-05-08 20:06:30 +00:00
form = $ ( '<form></form>' ) ;
form . append ( input ) ;
2018-11-15 11:14:15 +00:00
td . children ( 'a.name' ) . children ( ':not(.thumbnail-wrapper)' ) . hide ( ) ;
2014-05-08 20:06:30 +00:00
td . append ( form ) ;
input . focus ( ) ;
//preselect input
var len = input . val ( ) . lastIndexOf ( '.' ) ;
if ( len === - 1 ||
tr . data ( 'type' ) === 'dir' ) {
len = input . val ( ) . length ;
}
input . selectRange ( 0 , len ) ;
var checkInput = function ( ) {
var filename = input . val ( ) ;
2015-07-13 15:38:13 +00:00
if ( filename !== oldName ) {
2014-05-08 20:06:30 +00:00
// Files.isFileNameValid(filename) throws an exception itself
2014-05-12 17:54:20 +00:00
OCA . Files . Files . isFileNameValid ( filename ) ;
2014-05-08 20:06:30 +00:00
if ( self . inList ( filename ) ) {
2016-09-30 10:44:49 +00:00
throw t ( 'files' , '{newName} already exists' , { newName : filename } , undefined , {
escape : false
} ) ;
2014-05-08 20:06:30 +00:00
}
}
return true ;
} ;
2013-08-27 09:18:59 +00:00
2014-06-23 14:35:11 +00:00
function restore ( ) {
2015-08-07 12:00:44 +00:00
input . tooltip ( 'hide' ) ;
2014-06-23 14:35:11 +00:00
tr . data ( 'renaming' , false ) ;
form . remove ( ) ;
2018-11-15 11:14:15 +00:00
td . children ( 'a.name' ) . children ( ':not(.thumbnail-wrapper)' ) . show ( ) ;
2014-06-23 14:35:11 +00:00
}
2015-07-13 15:38:13 +00:00
function updateInList ( fileInfo ) {
2015-11-19 10:42:17 +00:00
self . updateRow ( tr , fileInfo ) ;
2018-01-11 01:47:11 +00:00
self . _updateDetailsView ( fileInfo . name , false ) ;
2015-07-13 15:38:13 +00:00
}
// TODO: too many nested blocks, move parts into functions
2014-05-08 20:06:30 +00:00
form . submit ( function ( event ) {
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
2014-06-23 14:35:11 +00:00
if ( input . hasClass ( 'error' ) ) {
return ;
}
2014-05-08 20:06:30 +00:00
try {
2019-01-31 14:30:20 +00:00
var newName = input . val ( ) . trim ( ) ;
2015-08-07 12:00:44 +00:00
input . tooltip ( 'hide' ) ;
2014-05-16 10:43:36 +00:00
form . remove ( ) ;
2015-07-13 15:38:13 +00:00
if ( newName !== oldName ) {
2014-05-08 20:06:30 +00:00
checkInput ( ) ;
2014-05-16 10:43:36 +00:00
// mark as loading (temp element)
2015-07-16 13:28:45 +00:00
self . showFileBusyState ( tr , true ) ;
2014-05-16 10:43:36 +00:00
tr . attr ( 'data-file' , newName ) ;
var basename = newName ;
if ( newName . indexOf ( '.' ) > 0 && tr . data ( 'type' ) !== 'dir' ) {
basename = newName . substr ( 0 , newName . lastIndexOf ( '.' ) ) ;
}
td . find ( 'a.name span.nametext' ) . text ( basename ) ;
2018-11-15 11:14:15 +00:00
td . children ( 'a.name' ) . children ( ':not(.thumbnail-wrapper)' ) . show ( ) ;
2014-05-16 10:43:36 +00:00
2015-07-13 15:38:13 +00:00
var path = tr . attr ( 'data-path' ) || self . getCurrentDirectory ( ) ;
2015-11-19 10:42:17 +00:00
self . filesClient . move ( OC . joinPaths ( path , oldName ) , OC . joinPaths ( path , newName ) )
2015-07-13 15:38:13 +00:00
. done ( function ( ) {
2015-11-19 10:42:17 +00:00
oldFileInfo . name = newName ;
updateInList ( oldFileInfo ) ;
2015-07-13 15:38:13 +00:00
} )
. fail ( function ( status ) {
// TODO: 409 means current folder does not exist, redirect ?
if ( status === 404 ) {
// source not found, so remove it from the list
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not rename "{fileName}", it does not exist any more' ,
2017-02-14 20:26:00 +00:00
{ fileName : oldName } ) , { timeout : 7 , type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
2017-02-14 20:26:00 +00:00
2015-07-13 15:38:13 +00:00
self . remove ( newName , { updateSummary : true } ) ;
return ;
} else if ( status === 412 ) {
// target exists
2017-02-14 20:26:00 +00:00
OC . Notification . show (
t ( 'files' , 'The name "{targetName}" is already used in the folder "{dir}". Please choose a different name.' ,
{
targetName : newName ,
dir : self . getCurrentDirectory ( ) ,
} ) ,
{
type : 'error'
}
2015-07-13 15:38:13 +00:00
) ;
} else {
// restore the item to its previous state
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not rename "{fileName}"' ,
2017-02-14 20:26:00 +00:00
{ fileName : oldName } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
2014-05-08 20:06:30 +00:00
}
2015-07-13 15:38:13 +00:00
updateInList ( oldFileInfo ) ;
} ) ;
2014-05-16 10:43:36 +00:00
} else {
// add back the old file info when cancelled
self . files . splice ( tr . index ( ) , 1 ) ;
tr . remove ( ) ;
2014-07-01 19:32:04 +00:00
tr = self . add ( oldFileInfo , { updateSummary : false , silent : true } ) ;
self . $fileList . trigger ( $ . Event ( 'fileActionsReady' , { fileList : self , $files : $ ( tr ) } ) ) ;
2014-05-08 20:06:30 +00:00
}
} catch ( error ) {
input . attr ( 'title' , error ) ;
2015-10-05 15:44:25 +00:00
input . tooltip ( { placement : 'right' , trigger : 'manual' } ) ;
2017-05-20 16:29:09 +00:00
input . tooltip ( 'fixTitle' ) ;
2015-08-07 12:00:44 +00:00
input . tooltip ( 'show' ) ;
2014-05-08 20:06:30 +00:00
input . addClass ( 'error' ) ;
}
return false ;
} ) ;
input . keyup ( function ( event ) {
// verify filename on typing
try {
checkInput ( ) ;
2015-08-07 12:00:44 +00:00
input . tooltip ( 'hide' ) ;
2014-05-08 20:06:30 +00:00
input . removeClass ( 'error' ) ;
} catch ( error ) {
input . attr ( 'title' , error ) ;
2015-10-05 15:44:25 +00:00
input . tooltip ( { placement : 'right' , trigger : 'manual' } ) ;
2017-05-20 16:29:09 +00:00
input . tooltip ( 'fixTitle' ) ;
2015-08-07 12:00:44 +00:00
input . tooltip ( 'show' ) ;
2014-05-08 20:06:30 +00:00
input . addClass ( 'error' ) ;
}
if ( event . keyCode === 27 ) {
2014-06-23 14:35:11 +00:00
restore ( ) ;
2014-05-08 20:06:30 +00:00
}
} ) ;
input . click ( function ( event ) {
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
} ) ;
input . blur ( function ( ) {
2018-02-21 17:46:27 +00:00
if ( input . hasClass ( 'error' ) ) {
restore ( ) ;
2018-04-18 13:38:30 +00:00
} else {
2018-02-21 17:46:27 +00:00
form . trigger ( 'submit' ) ;
}
2014-05-08 20:06:30 +00:00
} ) ;
} ,
2015-08-27 11:22:58 +00:00
/ * *
* Create an empty file inside the current directory .
*
* @ param { string } name name of the file
*
* @ return { Promise } promise that will be resolved after the
* file was created
2015-09-03 10:17:35 +00:00
*
* @ since 8.2
2015-08-27 11:22:58 +00:00
* /
2018-12-10 21:52:24 +00:00
createFile : function ( name , options ) {
2015-08-27 11:22:58 +00:00
var self = this ;
var deferred = $ . Deferred ( ) ;
var promise = deferred . promise ( ) ;
OCA . Files . Files . isFileNameValid ( name ) ;
if ( this . lastAction ) {
this . lastAction ( ) ;
}
2015-07-13 15:38:13 +00:00
name = this . getUniqueName ( name ) ;
var targetPath = this . getCurrentDirectory ( ) + '/' + name ;
self . filesClient . putFileContents (
targetPath ,
2016-11-16 09:47:20 +00:00
' ' , // dont create empty files which fails on some storage backends
2015-07-13 15:38:13 +00:00
{
contentType : 'text/plain' ,
overwrite : true
}
)
. done ( function ( ) {
// TODO: error handling / conflicts
2018-12-10 21:52:24 +00:00
options = _ . extend ( { scrollTo : true } , options || { } ) ;
self . addAndFetchFileInfo ( targetPath , '' , options ) . then ( function ( status , data ) {
2015-12-16 16:35:53 +00:00
deferred . resolve ( status , data ) ;
} , function ( ) {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create file "{file}"' ,
2017-02-14 20:26:00 +00:00
{ file : name } ) , { type : 'error' }
) ;
2015-12-16 16:35:53 +00:00
} ) ;
2015-07-13 15:38:13 +00:00
} )
. fail ( function ( status ) {
if ( status === 412 ) {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create file "{file}" because it already exists' ,
2017-02-14 20:26:00 +00:00
{ file : name } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
2015-08-27 11:22:58 +00:00
} else {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create file "{file}"' ,
2017-02-14 20:26:00 +00:00
{ file : name } ) , { type : 'error' }
) ;
2015-08-27 11:22:58 +00:00
}
2015-07-13 15:38:13 +00:00
deferred . reject ( status ) ;
} ) ;
2015-08-27 11:22:58 +00:00
return promise ;
} ,
/ * *
* Create a directory inside the current directory .
*
* @ param { string } name name of the directory
*
* @ return { Promise } promise that will be resolved after the
* directory was created
2015-09-03 10:17:35 +00:00
*
* @ since 8.2
2015-08-27 11:22:58 +00:00
* /
createDirectory : function ( name ) {
var self = this ;
var deferred = $ . Deferred ( ) ;
var promise = deferred . promise ( ) ;
OCA . Files . Files . isFileNameValid ( name ) ;
if ( this . lastAction ) {
this . lastAction ( ) ;
}
2015-07-13 15:38:13 +00:00
name = this . getUniqueName ( name ) ;
var targetPath = this . getCurrentDirectory ( ) + '/' + name ;
this . filesClient . createDirectory ( targetPath )
2015-12-16 16:35:53 +00:00
. done ( function ( ) {
self . addAndFetchFileInfo ( targetPath , '' , { scrollTo : true } ) . then ( function ( status , data ) {
deferred . resolve ( status , data ) ;
} , function ( ) {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create folder "{dir}"' ,
2017-02-14 20:26:00 +00:00
{ dir : name } ) , { type : 'error' }
) ;
2015-12-16 16:35:53 +00:00
} ) ;
2015-07-13 15:38:13 +00:00
} )
. fail ( function ( createStatus ) {
// method not allowed, folder might exist already
if ( createStatus === 405 ) {
2015-12-16 16:35:53 +00:00
// add it to the list, for completeness
self . addAndFetchFileInfo ( targetPath , '' , { scrollTo : true } )
2015-07-13 15:38:13 +00:00
. done ( function ( status , data ) {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create folder "{dir}" because it already exists' ,
2017-02-14 20:26:00 +00:00
{ dir : name } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
// still consider a failure
deferred . reject ( createStatus , data ) ;
} )
. fail ( function ( ) {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create folder "{dir}"' ,
2017-02-14 20:26:00 +00:00
{ dir : name } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
deferred . reject ( status ) ;
} ) ;
2015-08-27 11:22:58 +00:00
} else {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create folder "{dir}"' ,
2017-02-14 20:26:00 +00:00
{ dir : name } ) , { type : 'error' }
) ;
2015-07-13 15:38:13 +00:00
deferred . reject ( createStatus ) ;
2015-08-27 11:22:58 +00:00
}
2015-07-13 15:38:13 +00:00
} ) ;
2015-08-27 11:22:58 +00:00
return promise ;
} ,
2015-12-16 16:35:53 +00:00
/ * *
* Add file into the list by fetching its information from the server first .
*
* If the given directory does not match the current directory , nothing will
* be fetched .
*
* @ param { String } fileName file name
* @ param { String } [ dir ] optional directory , defaults to the current one
* @ param { Object } options same options as # add
* @ return { Promise } promise that resolves with the file info , or an
* already resolved Promise if no info was fetched . The promise rejects
* if the file was not found or an error occurred .
*
* @ since 9.0
* /
addAndFetchFileInfo : function ( fileName , dir , options ) {
var self = this ;
var deferred = $ . Deferred ( ) ;
if ( _ . isUndefined ( dir ) ) {
dir = this . getCurrentDirectory ( ) ;
} else {
dir = dir || '/' ;
}
var targetPath = OC . joinPaths ( dir , fileName ) ;
if ( ( OC . dirname ( targetPath ) || '/' ) !== this . getCurrentDirectory ( ) ) {
// no need to fetch information
deferred . resolve ( ) ;
return deferred . promise ( ) ;
}
var addOptions = _ . extend ( {
animate : true ,
scrollTo : false
} , options || { } ) ;
this . filesClient . getFileInfo ( targetPath , {
properties : this . _getWebdavProperties ( )
} )
. then ( function ( status , data ) {
// remove first to avoid duplicates
self . remove ( data . name ) ;
self . add ( data , addOptions ) ;
deferred . resolve ( status , data ) ;
} )
. fail ( function ( status ) {
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Could not create file "{file}"' ,
2017-02-14 20:26:00 +00:00
{ file : name } ) , { type : 'error' }
) ;
2015-12-16 16:35:53 +00:00
deferred . reject ( status ) ;
} ) ;
return deferred . promise ( ) ;
} ,
2015-08-27 11:22:58 +00:00
/ * *
* Returns whether the given file name exists in the list
*
* @ param { string } file file name
*
* @ return { bool } true if the file exists in the list , false otherwise
* /
2014-05-08 20:06:30 +00:00
inList : function ( file ) {
2015-09-28 15:50:11 +00:00
return this . findFile ( file ) ;
2014-05-08 20:06:30 +00:00
} ,
2015-07-16 13:28:45 +00:00
/ * *
* Shows busy state on a given file row or multiple
*
* @ param { string | Array . < string > } files file name or array of file names
* @ param { bool } [ busy = true ] busy state , true for busy , false to remove busy state
*
* @ since 8.2
* /
showFileBusyState : function ( files , state ) {
var self = this ;
2015-12-17 11:00:29 +00:00
if ( ! _ . isArray ( files ) && ! files . is ) {
2015-07-16 13:28:45 +00:00
files = [ files ] ;
}
if ( _ . isUndefined ( state ) ) {
state = true ;
}
2015-12-17 11:00:29 +00:00
_ . each ( files , function ( fileName ) {
2015-07-16 13:28:45 +00:00
// jquery element already ?
2015-12-17 11:00:29 +00:00
var $tr ;
if ( _ . isString ( fileName ) ) {
$tr = self . findFileEl ( fileName ) ;
} else {
$tr = $ ( fileName ) ;
2015-07-16 13:28:45 +00:00
}
var $thumbEl = $tr . find ( '.thumbnail' ) ;
$tr . toggleClass ( 'busy' , state ) ;
if ( state ) {
2017-09-21 17:46:29 +00:00
$thumbEl . parent ( ) . addClass ( 'icon-loading-small' ) ;
2015-07-16 13:28:45 +00:00
} else {
2017-09-21 17:46:29 +00:00
$thumbEl . parent ( ) . removeClass ( 'icon-loading-small' ) ;
2015-07-16 13:28:45 +00:00
}
} ) ;
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Delete the given files from the given dir
* @ param files file names list ( without path )
* @ param dir directory in which to delete the files , defaults to the current
* directory
* /
do _delete : function ( files , dir ) {
var self = this ;
if ( files && files . substr ) {
files = [ files ] ;
}
2015-07-13 15:38:13 +00:00
if ( ! files ) {
// delete all files in directory
files = _ . pluck ( this . files , 'name' ) ;
}
2014-05-08 20:06:30 +00:00
// Finish any existing actions
if ( this . lastAction ) {
this . lastAction ( ) ;
}
2014-04-04 14:38:27 +00:00
2015-07-13 15:38:13 +00:00
dir = dir || this . getCurrentDirectory ( ) ;
2018-11-19 21:52:19 +00:00
var removeFunction = function ( fileName ) {
var $tr = self . findFileEl ( fileName ) ;
self . showFileBusyState ( $tr , true ) ;
return self . filesClient . remove ( dir + '/' + fileName )
2015-07-13 15:38:13 +00:00
. done ( function ( ) {
2018-11-22 21:19:57 +00:00
if ( OC . joinPaths ( self . getCurrentDirectory ( ) , '/' ) === OC . joinPaths ( dir , '/' ) ) {
2018-11-19 21:52:19 +00:00
self . remove ( fileName ) ;
}
2015-07-13 15:38:13 +00:00
} )
. fail ( function ( status ) {
if ( status === 404 ) {
// the file already did not exist, remove it from the list
2018-11-22 21:19:57 +00:00
if ( OC . joinPaths ( self . getCurrentDirectory ( ) , '/' ) === OC . joinPaths ( dir , '/' ) ) {
2018-11-19 21:52:19 +00:00
self . remove ( fileName ) ;
}
2014-04-11 10:46:12 +00:00
} else {
2015-07-13 15:38:13 +00:00
// only reset the spinner for that one file
2017-11-04 08:45:29 +00:00
OC . Notification . show ( t ( 'files' , 'Error deleting file "{fileName}".' ,
2018-11-19 21:52:19 +00:00
{ fileName : fileName } ) , { type : 'error' }
2015-07-13 15:38:13 +00:00
) ;
2012-07-30 16:21:58 +00:00
}
2018-11-19 21:52:19 +00:00
} )
. always ( function ( ) {
self . showFileBusyState ( $tr , false ) ;
2012-07-30 16:21:58 +00:00
} ) ;
2018-11-19 21:52:19 +00:00
} ;
return this . reportOperationProgress ( files , removeFunction ) . then ( function ( ) {
self . updateStorageStatistics ( ) ;
self . updateStorageQuotas ( ) ;
} ) ;
2014-05-08 20:06:30 +00:00
} ,
2018-11-19 21:52:19 +00:00
2014-05-08 20:06:30 +00:00
/ * *
* Creates the file summary section
* /
_createSummary : function ( ) {
var $tr = $ ( '<tr class="summary"></tr>' ) ;
2017-09-28 23:36:10 +00:00
if ( this . _allowSelection ) {
// Dummy column for selection, as all rows must have the same
// number of columns.
$tr . append ( '<td></td>' ) ;
}
2014-05-08 20:06:30 +00:00
this . $el . find ( 'tfoot' ) . append ( $tr ) ;
2016-08-19 14:44:58 +00:00
return new OCA . Files . FileSummary ( $tr , { config : this . _filesConfig } ) ;
2014-05-08 20:06:30 +00:00
} ,
updateEmptyContent : function ( ) {
var permissions = this . getDirectoryPermissions ( ) ;
var isCreatable = ( permissions & OC . PERMISSION _CREATE ) !== 0 ;
2015-04-21 09:57:29 +00:00
this . $el . find ( '#emptycontent' ) . toggleClass ( 'hidden' , ! this . isEmpty ) ;
2018-10-23 06:18:14 +00:00
this . $el . find ( '#emptycontent' ) . toggleClass ( 'hidden' , ! this . isEmpty ) ;
2015-04-21 09:57:29 +00:00
this . $el . find ( '#emptycontent .uploadmessage' ) . toggleClass ( 'hidden' , ! isCreatable || ! this . isEmpty ) ;
2018-10-23 06:18:14 +00:00
this . $el . find ( '#filestable' ) . toggleClass ( 'hidden' , this . isEmpty ) ;
2014-05-08 20:06:30 +00:00
this . $el . find ( '#filestable thead th' ) . toggleClass ( 'hidden' , this . isEmpty ) ;
} ,
/ * *
* Shows the loading mask .
*
2014-06-23 21:56:10 +00:00
* @ see OCA . Files . FileList # hideMask
2014-05-08 20:06:30 +00:00
* /
showMask : function ( ) {
// in case one was shown before
var $mask = this . $el . find ( '.mask' ) ;
if ( $mask . exists ( ) ) {
return ;
2014-02-13 19:20:00 +00:00
}
2012-10-14 19:04:08 +00:00
2014-05-08 20:06:30 +00:00
this . $table . addClass ( 'hidden' ) ;
2015-08-07 13:57:16 +00:00
this . $el . find ( '#emptycontent' ) . addClass ( 'hidden' ) ;
2014-05-08 20:06:30 +00:00
2016-09-27 07:43:21 +00:00
$mask = $ ( '<div class="mask transparent icon-loading"></div>' ) ;
2014-05-08 20:06:30 +00:00
this . $el . append ( $mask ) ;
$mask . removeClass ( 'transparent' ) ;
} ,
/ * *
* Hide the loading mask .
2014-06-23 21:56:10 +00:00
* @ see OCA . Files . FileList # showMask
2014-05-08 20:06:30 +00:00
* /
hideMask : function ( ) {
this . $el . find ( '.mask' ) . remove ( ) ;
this . $table . removeClass ( 'hidden' ) ;
} ,
scrollTo : function ( file ) {
2014-09-04 10:20:11 +00:00
if ( ! _ . isArray ( file ) ) {
file = [ file ] ;
2014-05-08 20:06:30 +00:00
}
2017-01-23 17:06:24 +00:00
if ( file . length === 1 ) {
_ . defer ( function ( ) {
this . showDetailsView ( file [ 0 ] ) ;
} . bind ( this ) ) ;
}
2014-09-04 10:20:11 +00:00
this . highlightFiles ( file , function ( $tr ) {
$tr . addClass ( 'searchresult' ) ;
$tr . one ( 'hover' , function ( ) {
$tr . removeClass ( 'searchresult' ) ;
} ) ;
} ) ;
2014-05-08 20:06:30 +00:00
} ,
2014-12-18 09:26:41 +00:00
/ * *
* @ deprecated use setFilter ( filter )
* /
2014-05-08 20:06:30 +00:00
filter : function ( query ) {
2014-12-18 09:26:41 +00:00
this . setFilter ( '' ) ;
} ,
/ * *
* @ deprecated use setFilter ( '' )
* /
unfilter : function ( ) {
this . setFilter ( '' ) ;
} ,
/ * *
* hide files matching the given filter
* @ param filter
* /
setFilter : function ( filter ) {
2016-06-23 09:27:02 +00:00
var total = 0 ;
2016-06-30 09:10:48 +00:00
if ( this . _filter === filter ) {
return ;
}
2014-12-18 09:26:41 +00:00
this . _filter = filter ;
2014-12-18 22:11:42 +00:00
this . fileSummary . setFilter ( filter , this . files ) ;
2016-06-23 09:27:02 +00:00
total = this . fileSummary . getTotal ( ) ;
2015-01-09 09:49:22 +00:00
if ( ! this . $el . find ( '.mask' ) . exists ( ) ) {
this . hideIrrelevantUIWhenNoFilesMatch ( ) ;
}
2016-06-30 09:10:48 +00:00
2016-06-23 09:27:02 +00:00
var visibleCount = 0 ;
2015-10-29 15:30:56 +00:00
filter = filter . toLowerCase ( ) ;
2016-06-23 09:27:02 +00:00
function filterRows ( tr ) {
var $e = $ ( tr ) ;
2015-10-29 15:30:56 +00:00
if ( $e . data ( 'file' ) . toString ( ) . toLowerCase ( ) . indexOf ( filter ) === - 1 ) {
2014-12-18 09:26:41 +00:00
$e . addClass ( 'hidden' ) ;
} else {
2016-06-23 09:27:02 +00:00
visibleCount ++ ;
2014-12-18 09:26:41 +00:00
$e . removeClass ( 'hidden' ) ;
2014-05-08 20:06:30 +00:00
}
2016-06-23 09:27:02 +00:00
}
var $trs = this . $fileList . find ( 'tr' ) ;
do {
_ . each ( $trs , filterRows ) ;
if ( visibleCount < total ) {
$trs = this . _nextPage ( false ) ;
}
2016-06-30 09:10:48 +00:00
} while ( visibleCount < total && $trs . length > 0 ) ;
2016-06-23 09:27:02 +00:00
this . $container . trigger ( 'scroll' ) ;
2014-05-08 20:06:30 +00:00
} ,
2014-12-18 23:49:05 +00:00
hideIrrelevantUIWhenNoFilesMatch : function ( ) {
2014-12-18 22:11:42 +00:00
if ( this . _filter && this . fileSummary . summary . totalDirs + this . fileSummary . summary . totalFiles === 0 ) {
this . $el . find ( '#filestable thead th' ) . addClass ( 'hidden' ) ;
2014-12-18 23:49:05 +00:00
this . $el . find ( '#emptycontent' ) . addClass ( 'hidden' ) ;
2015-06-22 13:30:29 +00:00
$ ( '#searchresults' ) . addClass ( 'filter-empty' ) ;
2016-03-31 13:26:37 +00:00
$ ( '#searchresults .emptycontent' ) . addClass ( 'emptycontent-search' ) ;
2015-01-09 09:49:22 +00:00
if ( $ ( '#searchresults' ) . length === 0 || $ ( '#searchresults' ) . hasClass ( 'hidden' ) ) {
2017-03-15 11:53:44 +00:00
var error = t ( 'files' , 'No search results in other folders for {tag}{filter}{endtag}' , { filter : this . _filter } ) ;
2015-01-06 13:34:35 +00:00
this . $el . find ( '.nofilterresults' ) . removeClass ( 'hidden' ) .
2016-10-11 12:21:37 +00:00
find ( 'p' ) . html ( error . replace ( '{tag}' , '<strong>' ) . replace ( '{endtag}' , '</strong>' ) ) ;
2015-01-02 11:50:21 +00:00
}
2014-12-18 22:11:42 +00:00
} else {
2015-06-22 13:30:29 +00:00
$ ( '#searchresults' ) . removeClass ( 'filter-empty' ) ;
2016-03-31 13:26:37 +00:00
$ ( '#searchresults .emptycontent' ) . removeClass ( 'emptycontent-search' ) ;
2014-12-18 23:49:05 +00:00
this . $el . find ( '#filestable thead th' ) . toggleClass ( 'hidden' , this . isEmpty ) ;
2015-01-07 14:59:34 +00:00
if ( ! this . $el . find ( '.mask' ) . exists ( ) ) {
this . $el . find ( '#emptycontent' ) . toggleClass ( 'hidden' , ! this . isEmpty ) ;
}
2015-01-06 13:34:35 +00:00
this . $el . find ( '.nofilterresults' ) . addClass ( 'hidden' ) ;
2014-12-18 22:11:42 +00:00
}
} ,
2014-12-18 09:26:41 +00:00
/ * *
* get the current filter
* @ param filter
* /
getFilter : function ( filter ) {
return this . _filter ;
2014-05-08 20:06:30 +00:00
} ,
2015-01-05 12:11:50 +00:00
/ * *
* update the search object to use this filelist when filtering
* /
updateSearch : function ( ) {
if ( OCA . Search . files ) {
OCA . Search . files . setFileList ( this ) ;
}
2015-01-05 16:53:14 +00:00
if ( OC . Search ) {
OC . Search . clear ( ) ;
}
2015-01-05 12:11:50 +00:00
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Update UI based on the current selection
* /
updateSelectionSummary : function ( ) {
var summary = this . _selectionSummary . summary ;
2015-08-18 07:31:02 +00:00
var selection ;
2016-08-19 14:44:58 +00:00
var showHidden = ! ! this . _filesConfig . get ( 'showhidden' ) ;
2014-05-08 20:06:30 +00:00
if ( summary . totalFiles === 0 && summary . totalDirs === 0 ) {
this . $el . find ( '#headerName a.name>span:first' ) . text ( t ( 'files' , 'Name' ) ) ;
this . $el . find ( '#headerSize a>span:first' ) . text ( t ( 'files' , 'Size' ) ) ;
this . $el . find ( '#modified a>span:first' ) . text ( t ( 'files' , 'Modified' ) ) ;
this . $el . find ( 'table' ) . removeClass ( 'multiselect' ) ;
this . $el . find ( '.selectedActions' ) . addClass ( 'hidden' ) ;
2013-07-31 20:24:52 +00:00
}
2014-05-08 20:06:30 +00:00
else {
this . $el . find ( '.selectedActions' ) . removeClass ( 'hidden' ) ;
this . $el . find ( '#headerSize a>span:first' ) . text ( OC . Util . humanFileSize ( summary . totalSize ) ) ;
2015-08-14 11:50:25 +00:00
var directoryInfo = n ( 'files' , '%n folder' , '%n folders' , summary . totalDirs ) ;
var fileInfo = n ( 'files' , '%n file' , '%n files' , summary . totalFiles ) ;
if ( summary . totalDirs > 0 && summary . totalFiles > 0 ) {
2015-08-17 12:28:55 +00:00
var selectionVars = {
dirs : directoryInfo ,
files : fileInfo
} ;
2015-08-18 07:31:02 +00:00
selection = t ( 'files' , '{dirs} and {files}' , selectionVars ) ;
2015-08-14 11:50:25 +00:00
} else if ( summary . totalDirs > 0 ) {
2015-08-18 07:31:02 +00:00
selection = directoryInfo ;
2015-08-14 11:50:25 +00:00
} else {
2015-08-18 07:31:02 +00:00
selection = fileInfo ;
2014-02-12 13:50:23 +00:00
}
2015-08-14 11:50:25 +00:00
2016-08-19 14:44:58 +00:00
if ( ! showHidden && summary . totalHidden > 0 ) {
var hiddenInfo = n ( 'files' , 'including %n hidden' , 'including %n hidden' , summary . totalHidden ) ;
selection += ' (' + hiddenInfo + ')' ;
}
2014-05-08 20:06:30 +00:00
this . $el . find ( '#headerName a.name>span:first' ) . text ( selection ) ;
this . $el . find ( '#modified a>span:first' ) . text ( '' ) ;
this . $el . find ( 'table' ) . addClass ( 'multiselect' ) ;
2018-02-06 15:44:04 +00:00
2018-06-06 18:01:25 +00:00
if ( this . fileMultiSelectMenu ) {
this . fileMultiSelectMenu . toggleItemVisibility ( 'download' , this . isSelectedDownloadable ( ) ) ;
this . fileMultiSelectMenu . toggleItemVisibility ( 'delete' , this . isSelectedDeletable ( ) ) ;
this . fileMultiSelectMenu . toggleItemVisibility ( 'copyMove' , this . isSelectedCopiable ( ) ) ;
if ( this . isSelectedCopiable ( ) ) {
if ( this . isSelectedMovable ( ) ) {
this . fileMultiSelectMenu . updateItemText ( 'copyMove' , t ( 'files' , 'Move or copy' ) ) ;
} else {
this . fileMultiSelectMenu . updateItemText ( 'copyMove' , t ( 'files' , 'Copy' ) ) ;
}
2018-02-06 15:44:04 +00:00
} else {
2018-06-06 18:01:25 +00:00
this . fileMultiSelectMenu . toggleItemVisibility ( 'copyMove' , false ) ;
2018-02-06 15:44:04 +00:00
}
}
2014-02-12 13:50:23 +00:00
}
2014-05-08 20:06:30 +00:00
} ,
2017-11-02 11:47:57 +00:00
/ * *
2018-02-06 15:44:04 +00:00
* Check whether all selected files are copiable
* /
isSelectedCopiable : function ( ) {
return _ . reduce ( this . getSelectedFiles ( ) , function ( copiable , file ) {
var requiredPermission = $ ( '#isPublic' ) . val ( ) ? OC . PERMISSION _UPDATE : OC . PERMISSION _READ ;
return copiable && ( file . permissions & requiredPermission ) ;
} , true ) ;
} ,
/ * *
* Check whether all selected files are movable
2017-11-02 11:47:57 +00:00
* /
2018-02-06 15:44:04 +00:00
isSelectedMovable : function ( ) {
return _ . reduce ( this . getSelectedFiles ( ) , function ( movable , file ) {
return movable && ( file . permissions & OC . PERMISSION _UPDATE ) ;
2017-11-02 11:47:57 +00:00
} , true ) ;
} ,
/ * *
* Check whether all selected files are downloadable
* /
isSelectedDownloadable : function ( ) {
return _ . reduce ( this . getSelectedFiles ( ) , function ( downloadable , file ) {
return downloadable && ( file . permissions & OC . PERMISSION _READ ) ;
} , true ) ;
} ,
2014-11-20 15:53:32 +00:00
/ * *
* Check whether all selected files are deletable
* /
isSelectedDeletable : function ( ) {
return _ . reduce ( this . getSelectedFiles ( ) , function ( deletable , file ) {
return deletable && ( file . permissions & OC . PERMISSION _DELETE ) ;
} , true ) ;
} ,
2014-05-08 20:06:30 +00:00
/ * *
2019-04-30 10:26:37 +00:00
* Are all files selected ?
2019-10-14 17:19:16 +00:00
*
2019-04-30 10:26:37 +00:00
* @ returns { Boolean } all files are selected
2014-05-08 20:06:30 +00:00
* /
isAllSelected : function ( ) {
2019-04-30 10:26:37 +00:00
var checkbox = this . $el . find ( '.select-all' )
var checked = checkbox . prop ( 'checked' )
var indeterminate = checkbox . prop ( 'indeterminate' )
return checked && ! indeterminate ;
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Returns the file info of the selected files
*
* @ return array of file names
* /
getSelectedFiles : function ( ) {
return _ . values ( this . _selectedFiles ) ;
} ,
getUniqueName : function ( name ) {
if ( this . findFileEl ( name ) . exists ( ) ) {
var numMatch ;
var parts = name . split ( '.' ) ;
var extension = "" ;
if ( parts . length > 1 ) {
extension = parts . pop ( ) ;
}
var base = parts . join ( '.' ) ;
numMatch = base . match ( /\((\d+)\)/ ) ;
var num = 2 ;
if ( numMatch && numMatch . length > 0 ) {
2014-07-04 12:08:48 +00:00
num = parseInt ( numMatch [ numMatch . length - 1 ] , 10 ) + 1 ;
2014-05-08 20:06:30 +00:00
base = base . split ( '(' ) ;
base . pop ( ) ;
base = $ . trim ( base . join ( '(' ) ) ;
}
name = base + ' (' + num + ')' ;
if ( extension ) {
name = name + '.' + extension ;
}
// FIXME: ugly recursion
return this . getUniqueName ( name ) ;
2014-02-12 13:50:23 +00:00
}
2014-05-08 20:06:30 +00:00
return name ;
} ,
2014-05-12 17:54:20 +00:00
2014-09-04 17:58:49 +00:00
/ * *
* Shows a "permission denied" notification
* /
_showPermissionDeniedNotification : function ( ) {
2017-08-24 12:54:14 +00:00
var message = t ( 'files' , 'You don’ t have permission to upload or create files here' ) ;
2017-02-14 20:26:00 +00:00
OC . Notification . show ( message , { type : 'error' } ) ;
2014-09-04 17:58:49 +00:00
} ,
2014-05-12 17:54:20 +00:00
/ * *
* Setup file upload events related to the file - upload plugin
2016-07-15 14:03:02 +00:00
*
* @ param { OC . Uploader } uploader
2014-05-12 17:54:20 +00:00
* /
2016-07-15 14:03:02 +00:00
setupUploadEvents : function ( uploader ) {
2014-05-08 20:06:30 +00:00
var self = this ;
2015-12-16 16:35:53 +00:00
self . _uploads = { } ;
2014-05-08 20:06:30 +00:00
2014-05-23 17:02:50 +00:00
// detect the progress bar resize
2016-07-15 14:03:02 +00:00
uploader . on ( 'resized' , this . _onResize ) ;
2014-05-23 17:02:50 +00:00
2016-07-15 14:03:02 +00:00
uploader . on ( 'drop' , function ( e , data ) {
2015-12-16 16:35:53 +00:00
self . _uploader . log ( 'filelist handle fileuploaddrop' , e , data ) ;
2014-05-08 20:06:30 +00:00
2015-01-12 11:29:26 +00:00
if ( self . $el . hasClass ( 'hidden' ) ) {
// do not upload to invisible lists
2017-07-10 12:32:10 +00:00
e . preventDefault ( ) ;
2015-01-12 11:29:26 +00:00
return false ;
}
2016-06-14 09:51:03 +00:00
var dropTarget = $ ( e . delegatedEvent . target ) ;
2014-05-22 09:16:42 +00:00
// check if dropped inside this container and not another one
2015-01-12 11:29:26 +00:00
if ( dropTarget . length
&& ! self . $el . is ( dropTarget ) // dropped on list directly
&& ! self . $el . has ( dropTarget ) . length // dropped inside list
&& ! dropTarget . is ( self . $container ) // dropped on main container
2019-05-27 11:00:45 +00:00
&& ! self . $el . parent ( ) . is ( dropTarget ) // drop on the parent container (#app-content) since the main container might not have the full height
2015-01-12 11:29:26 +00:00
) {
2017-07-10 12:32:10 +00:00
e . preventDefault ( ) ;
2014-05-12 17:54:20 +00:00
return false ;
}
2014-05-21 13:55:29 +00:00
// find the closest tr or crumb to use as target
dropTarget = dropTarget . closest ( 'tr, .crumb' ) ;
// if dropping on tr or crumb, drag&drop upload to folder
if ( dropTarget && ( dropTarget . data ( 'type' ) === 'dir' ||
dropTarget . hasClass ( 'crumb' ) ) ) {
2014-05-08 20:06:30 +00:00
// remember as context
data . context = dropTarget ;
2014-09-04 17:58:49 +00:00
// if permissions are specified, only allow if create permission is there
var permissions = dropTarget . data ( 'permissions' ) ;
if ( ! _ . isUndefined ( permissions ) && ( permissions & OC . PERMISSION _CREATE ) === 0 ) {
self . _showPermissionDeniedNotification ( ) ;
return false ;
}
2014-05-08 20:06:30 +00:00
var dir = dropTarget . data ( 'file' ) ;
// if from file list, need to prepend parent dir
if ( dir ) {
var parentDir = self . getCurrentDirectory ( ) ;
if ( parentDir [ parentDir . length - 1 ] !== '/' ) {
parentDir += '/' ;
}
dir = parentDir + dir ;
}
else {
// read full path from crumb
dir = dropTarget . data ( 'dir' ) || '/' ;
}
2013-09-08 15:29:43 +00:00
2014-06-18 18:38:51 +00:00
// add target dir
data . targetDir = dir ;
2014-05-08 20:06:30 +00:00
} else {
// cancel uploads to current dir if no permission
2014-05-12 17:54:20 +00:00
var isCreatable = ( self . getDirectoryPermissions ( ) & OC . PERMISSION _CREATE ) !== 0 ;
2014-05-08 20:06:30 +00:00
if ( ! isCreatable ) {
2014-09-04 17:58:49 +00:00
self . _showPermissionDeniedNotification ( ) ;
2017-08-24 12:55:17 +00:00
e . stopPropagation ( ) ;
2014-05-08 20:06:30 +00:00
return false ;
}
2013-09-08 15:29:43 +00:00
2016-08-31 14:32:14 +00:00
// we are dropping somewhere inside the file list, which will
// upload the file to the current directory
data . targetDir = self . getCurrentDirectory ( ) ;
2014-05-08 20:06:30 +00:00
}
} ) ;
2016-07-15 14:03:02 +00:00
uploader . on ( 'add' , function ( e , data ) {
2015-12-16 16:35:53 +00:00
self . _uploader . log ( 'filelist handle fileuploadadd' , e , data ) ;
2013-09-08 15:29:43 +00:00
2014-05-08 20:06:30 +00:00
// add ui visualization to existing folder
if ( data . context && data . context . data ( 'type' ) === 'dir' ) {
// add to existing folder
// update upload counter ui
var uploadText = data . context . find ( '.uploadtext' ) ;
var currentUploads = parseInt ( uploadText . attr ( 'currentUploads' ) , 10 ) ;
currentUploads += 1 ;
uploadText . attr ( 'currentUploads' , currentUploads ) ;
var translatedText = n ( 'files' , 'Uploading %n file' , 'Uploading %n files' , currentUploads ) ;
if ( currentUploads === 1 ) {
2015-12-17 11:00:29 +00:00
self . showFileBusyState ( uploadText . closest ( 'tr' ) , true ) ;
2014-05-08 20:06:30 +00:00
uploadText . text ( translatedText ) ;
uploadText . show ( ) ;
} else {
uploadText . text ( translatedText ) ;
}
}
2013-09-08 15:29:43 +00:00
2015-12-16 16:35:53 +00:00
if ( ! data . targetDir ) {
data . targetDir = self . getCurrentDirectory ( ) ;
}
2014-05-08 20:06:30 +00:00
} ) ;
/ *
* when file upload done successfully add row to filelist
* update counter when uploading to sub folder
* /
2016-07-15 14:03:02 +00:00
uploader . on ( 'done' , function ( e , upload ) {
2018-10-19 10:01:50 +00:00
var data = upload . data ;
2015-12-16 16:35:53 +00:00
self . _uploader . log ( 'filelist handle fileuploaddone' , e , data ) ;
2014-05-08 20:06:30 +00:00
2015-12-16 16:35:53 +00:00
var status = data . jqXHR . status ;
if ( status < 200 || status >= 300 ) {
// error was handled in OC.Uploads already
return ;
2013-10-15 14:14:23 +00:00
}
2014-05-26 18:32:24 +00:00
2015-12-16 16:35:53 +00:00
var fileName = upload . getFileName ( ) ;
var fetchInfoPromise = self . addAndFetchFileInfo ( fileName , upload . getFullPath ( ) ) ;
if ( ! self . _uploads ) {
self . _uploads = { } ;
}
if ( OC . isSamePath ( OC . dirname ( upload . getFullPath ( ) + '/' ) , self . getCurrentDirectory ( ) ) ) {
self . _uploads [ fileName ] = fetchInfoPromise ;
2014-05-08 20:06:30 +00:00
}
2015-12-17 11:00:29 +00:00
var uploadText = self . $fileList . find ( 'tr .uploadtext' ) ;
self . showFileBusyState ( uploadText . closest ( 'tr' ) , false ) ;
uploadText . fadeOut ( ) ;
uploadText . attr ( 'currentUploads' , 0 ) ;
2018-01-03 12:59:36 +00:00
2018-01-03 13:01:47 +00:00
self . updateStorageQuotas ( ) ;
2014-05-08 20:06:30 +00:00
} ) ;
2016-09-03 16:06:35 +00:00
uploader . on ( 'createdfolder' , function ( fullPath ) {
2015-12-16 16:35:53 +00:00
self . addAndFetchFileInfo ( OC . basename ( fullPath ) , OC . dirname ( fullPath ) ) ;
} ) ;
2016-07-15 14:03:02 +00:00
uploader . on ( 'stop' , function ( ) {
2015-12-16 16:35:53 +00:00
self . _uploader . log ( 'filelist handle fileuploadstop' ) ;
// prepare list of uploaded file names in the current directory
// and discard the other ones
var promises = _ . values ( self . _uploads ) ;
var fileNames = _ . keys ( self . _uploads ) ;
self . _uploads = [ ] ;
// as soon as all info is fetched
$ . when . apply ( $ , promises ) . then ( function ( ) {
// highlight uploaded files
self . highlightFiles ( fileNames ) ;
2016-09-21 16:49:15 +00:00
self . updateStorageStatistics ( ) ;
2015-12-16 16:35:53 +00:00
} ) ;
2016-09-03 16:06:35 +00:00
var uploadText = self . $fileList . find ( 'tr .uploadtext' ) ;
self . showFileBusyState ( uploadText . closest ( 'tr' ) , false ) ;
uploadText . fadeOut ( ) ;
uploadText . attr ( 'currentUploads' , 0 ) ;
2014-05-08 20:06:30 +00:00
} ) ;
2016-07-15 14:03:02 +00:00
uploader . on ( 'fail' , function ( e , data ) {
2015-12-16 16:35:53 +00:00
self . _uploader . log ( 'filelist handle fileuploadfail' , e , data ) ;
self . _uploads = [ ] ;
2013-09-08 15:29:43 +00:00
2014-05-08 20:06:30 +00:00
//if user pressed cancel hide upload chrome
2016-09-03 16:06:35 +00:00
//cleanup uploading to a dir
var uploadText = self . $fileList . find ( 'tr .uploadtext' ) ;
self . showFileBusyState ( uploadText . closest ( 'tr' ) , false ) ;
uploadText . fadeOut ( ) ;
uploadText . attr ( 'currentUploads' , 0 ) ;
2014-05-12 17:54:20 +00:00
self . updateStorageStatistics ( ) ;
2014-05-08 20:06:30 +00:00
} ) ;
2013-03-13 16:26:37 +00:00
2014-06-19 18:11:57 +00:00
} ,
/ * *
* Scroll to the last file of the given list
* Highlight the list of files
2014-09-04 10:20:11 +00:00
* @ param files array of filenames ,
* @ param { Function } [ highlightFunction ] optional function
* to be called after the scrolling is finished
2014-06-19 18:11:57 +00:00
* /
2014-09-04 10:20:11 +00:00
highlightFiles : function ( files , highlightFunction ) {
2014-06-19 18:11:57 +00:00
// Detection of the uploaded element
var filename = files [ files . length - 1 ] ;
var $fileRow = this . findFileEl ( filename ) ;
while ( ! $fileRow . exists ( ) && this . _nextPage ( false ) !== false ) { // Checking element existence
$fileRow = this . findFileEl ( filename ) ;
}
if ( ! $fileRow . exists ( ) ) { // Element not present in the file list
return ;
}
var currentOffset = this . $container . scrollTop ( ) ;
var additionalOffset = this . $el . find ( "#controls" ) . height ( ) + this . $el . find ( "#controls" ) . offset ( ) . top ;
// Animation
var _this = this ;
2014-10-15 08:14:20 +00:00
var $scrollContainer = this . $container ;
if ( $scrollContainer [ 0 ] === window ) {
2019-06-19 08:56:59 +00:00
// need to use "html" to animate scrolling
2014-10-15 08:14:20 +00:00
// when the scroll container is the window
2019-06-19 08:56:59 +00:00
$scrollContainer = $ ( 'html' ) ;
2014-10-15 08:14:20 +00:00
}
$scrollContainer . animate ( {
2014-06-19 18:11:57 +00:00
// Scrolling to the top of the new element
scrollTop : currentOffset + $fileRow . offset ( ) . top - $fileRow . height ( ) * 2 - additionalOffset
} , {
duration : 500 ,
complete : function ( ) {
// Highlighting function
2014-09-04 10:20:11 +00:00
var highlightRow = highlightFunction ;
if ( ! highlightRow ) {
highlightRow = function ( $fileRow ) {
$fileRow . addClass ( "highlightUploaded" ) ;
setTimeout ( function ( ) {
$fileRow . removeClass ( "highlightUploaded" ) ;
} , 2500 ) ;
} ;
}
2014-06-19 18:11:57 +00:00
// Loop over uploaded files
for ( var i = 0 ; i < files . length ; i ++ ) {
var $fileRow = _this . findFileEl ( files [ i ] ) ;
if ( $fileRow . length !== 0 ) { // Checking element existence
highlightRow ( $fileRow ) ;
}
}
}
} ) ;
2015-07-15 15:05:25 +00:00
} ,
2015-08-27 11:22:58 +00:00
_renderNewButton : function ( ) {
2015-09-22 14:47:52 +00:00
// if an upload button (legacy) already exists or no actions container exist, skip
var $actionsContainer = this . $el . find ( '#controls .actions' ) ;
if ( ! $actionsContainer . length || this . $el . find ( '.button.upload' ) . length ) {
2015-08-27 11:22:58 +00:00
return ;
}
2018-10-01 16:50:09 +00:00
var $newButton = $ ( OCA . Files . Templates [ 'template_addbutton' ] ( {
2015-08-27 11:22:58 +00:00
addText : t ( 'files' , 'New' ) ,
2016-02-17 10:04:29 +00:00
iconClass : 'icon-add'
2015-08-27 11:22:58 +00:00
} ) ) ;
2015-09-22 14:47:52 +00:00
$actionsContainer . prepend ( $newButton ) ;
2015-08-27 11:22:58 +00:00
$newButton . tooltip ( { 'placement' : 'bottom' } ) ;
$newButton . click ( _ . bind ( this . _onClickNewButton , this ) ) ;
this . _newButton = $newButton ;
} ,
_onClickNewButton : function ( event ) {
var $target = $ ( event . target ) ;
if ( ! $target . hasClass ( '.button' ) ) {
$target = $target . closest ( '.button' ) ;
}
this . _newButton . tooltip ( 'hide' ) ;
event . preventDefault ( ) ;
if ( $target . hasClass ( 'disabled' ) ) {
return false ;
}
if ( ! this . _newFileMenu ) {
this . _newFileMenu = new OCA . Files . NewFileMenu ( {
fileList : this
} ) ;
2017-01-21 19:05:58 +00:00
$ ( '.actions' ) . append ( this . _newFileMenu . $el ) ;
2015-08-27 11:22:58 +00:00
}
this . _newFileMenu . showAt ( $target ) ;
return false ;
} ,
2015-07-15 15:05:25 +00:00
/ * *
* Register a tab view to be added to all views
* /
registerTabView : function ( tabView ) {
2015-10-15 14:30:50 +00:00
if ( this . _detailsView ) {
this . _detailsView . addTabView ( tabView ) ;
}
2015-07-15 15:05:25 +00:00
} ,
/ * *
* Register a detail view to be added to all views
* /
registerDetailView : function ( detailView ) {
2015-10-15 14:30:50 +00:00
if ( this . _detailsView ) {
this . _detailsView . addDetailView ( detailView ) ;
}
2016-10-04 10:56:04 +00:00
} ,
/ * *
* Register a view to be added to the breadcrumb view
* /
registerBreadCrumbDetailView : function ( detailView ) {
if ( this . breadcrumb ) {
this . breadcrumb . addDetailView ( detailView ) ;
}
2017-06-09 01:14:23 +00:00
} ,
/ * *
* Returns the registered detail views .
*
* @ return null | Array < OCA . Files . DetailFileInfoView > an array with the
* registered DetailFileInfoViews , or null if the details view
* is not enabled .
* /
getRegisteredDetailViews : function ( ) {
if ( this . _detailsView ) {
return this . _detailsView . getDetailViews ( ) ;
}
return null ;
2019-08-19 09:40:42 +00:00
} ,
registerHeader : function ( header ) {
this . headers . push (
_ . defaults ( header , { order : 0 } )
) ;
} ,
registerFooter : function ( footer ) {
this . footers . push (
_ . defaults ( footer , { order : 0 } )
) ;
2014-05-08 20:06:30 +00:00
}
} ;
2013-09-08 15:29:43 +00:00
2019-01-13 16:15:43 +00:00
FileList . MultiSelectMenuActions = {
ToggleSelectionModeAction : function ( fileList ) {
return {
name : 'toggleSelectionMode' ,
displayName : function ( context ) {
return t ( 'files' , 'Select file range' ) ;
} ,
iconClass : 'icon-fullscreen' ,
action : function ( ) {
fileList . _onClickToggleSelectionMode ( ) ;
} ,
} ;
} ,
} ,
2014-05-08 20:06:30 +00:00
/ * *
* Sort comparators .
2014-06-23 21:56:10 +00:00
* @ namespace OCA . Files . FileList . Comparators
* @ private
2014-05-08 20:06:30 +00:00
* /
FileList . Comparators = {
/ * *
* Compares two file infos by name , making directories appear
* first .
*
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileInfo1 file info
* @ param { OC . Files . FileInfo } fileInfo2 file info
2014-06-23 21:56:10 +00:00
* @ return { int } - 1 if the first file must appear before the second one ,
2014-05-08 20:06:30 +00:00
* 0 if they are identify , 1 otherwise .
* /
name : function ( fileInfo1 , fileInfo2 ) {
if ( fileInfo1 . type === 'dir' && fileInfo2 . type !== 'dir' ) {
return - 1 ;
2013-03-13 16:26:37 +00:00
}
2014-05-08 20:06:30 +00:00
if ( fileInfo1 . type !== 'dir' && fileInfo2 . type === 'dir' ) {
return 1 ;
}
2014-02-18 11:29:05 +00:00
return OC . Util . naturalSortCompare ( fileInfo1 . name , fileInfo2 . name ) ;
2014-05-08 20:06:30 +00:00
} ,
/ * *
* Compares two file infos by size .
*
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileInfo1 file info
* @ param { OC . Files . FileInfo } fileInfo2 file info
2014-06-23 21:56:10 +00:00
* @ return { int } - 1 if the first file must appear before the second one ,
2014-05-08 20:06:30 +00:00
* 0 if they are identify , 1 otherwise .
* /
size : function ( fileInfo1 , fileInfo2 ) {
return fileInfo1 . size - fileInfo2 . size ;
} ,
/ * *
* Compares two file infos by timestamp .
*
2015-07-13 15:38:13 +00:00
* @ param { OC . Files . FileInfo } fileInfo1 file info
* @ param { OC . Files . FileInfo } fileInfo2 file info
2014-06-23 21:56:10 +00:00
* @ return { int } - 1 if the first file must appear before the second one ,
2014-05-08 20:06:30 +00:00
* 0 if they are identify , 1 otherwise .
* /
mtime : function ( fileInfo1 , fileInfo2 ) {
return fileInfo1 . mtime - fileInfo2 . mtime ;
2013-03-13 16:26:37 +00:00
}
2014-05-08 20:06:30 +00:00
} ;
2013-09-08 15:29:43 +00:00
2014-06-23 21:56:10 +00:00
/ * *
* File info attributes .
*
2015-07-13 15:38:13 +00:00
* @ typedef { Object } OC . Files . FileInfo
*
* @ lends OC . Files . FileInfo
*
* @ deprecated use OC . Files . FileInfo instead
2014-06-23 21:56:10 +00:00
*
* /
2015-07-13 15:38:13 +00:00
OCA . Files . FileInfo = OC . Files . FileInfo ;
2014-06-23 21:56:10 +00:00
2014-05-08 20:06:30 +00:00
OCA . Files . FileList = FileList ;
} ) ( ) ;
$ ( document ) . ready ( function ( ) {
// FIXME: unused ?
OCA . Files . FileList . useUndo = ( window . onbeforeunload ) ? true : false ;
2018-10-09 10:00:48 +00:00
$ ( window ) . on ( 'beforeunload' , function ( ) {
2014-05-08 20:06:30 +00:00
if ( OCA . Files . FileList . lastAction ) {
OCA . Files . FileList . lastAction ( ) ;
2012-07-30 16:21:58 +00:00
}
2011-08-03 22:22:44 +00:00
} ) ;
2016-04-19 10:05:09 +00:00
$ ( window ) . on ( 'unload' , function ( ) {
2012-11-05 17:42:44 +00:00
$ ( window ) . trigger ( 'beforeunload' ) ;
} ) ;
2013-07-03 17:50:03 +00:00
2011-08-03 22:22:44 +00:00
} ) ;