]>
git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/blob - frontend/js/app.js
9a6251b37d036e1d84427d22a6748ce27c16430a
4 /* global superagent */
10 function asyncForEach ( items
, handler
, callback
) {
13 if ( items
. length
=== 0 ) return callback ();
15 ( function iterator () {
16 handler ( items
[ cur
], function ( error
) {
17 if ( error
) return callback ( error
);
18 if ( cur
>= items
. length
- 1 ) return callback ();
26 function getProfile ( accessToken
, callback
) {
27 superagent
. get ( '/api/profile' ). query ({ access_token : accessToken
}). end ( function ( error
, result
) {
30 if ( error
&& ! error
. response
) return callback ( error
);
31 if ( result
. statusCode
!== 200 ) {
32 delete localStorage
. accessToken
;
33 return callback ( 'Invalid access token' );
36 localStorage
. accessToken
= accessToken
;
37 app
. session
. username
= result
. body
. username
;
38 app
. session
. valid
= true ;
40 superagent
. get ( '/api/settings' ). query ({ access_token : localStorage
. accessToken
}). end ( function ( error
, result
) {
41 if ( error
) console
. error ( error
);
43 app
. folderListingEnabled
= !! result
. body
. folderListingEnabled
;
50 function sanitize ( filePath
) {
51 filePath
= '/' + filePath
;
52 return filePath
. replace ( /\/+/g , '/' );
55 function encode ( filePath
) {
56 return filePath
. split ( '/' ). map ( encodeURIComponent
). join ( '/' );
59 function decode ( filePath
) {
60 return filePath
. split ( '/' ). map ( decodeURIComponent
). join ( '/' );
64 images : [ '.png' , '.jpg' , '.jpeg' , '.tiff' , '.gif' ],
65 text : [ '.txt' , '.md' ],
67 html : [ '.html' , '.htm' , '.php' ],
68 video : [ '.mp4' , '.mpg' , '.mpeg' , '.ogg' , '.mkv' , '.avi' , '.mov' ]
71 function getPreviewUrl ( entry
, basePath
) {
72 var path
= '/_admin/img/' ;
74 if ( entry
. isDirectory
) return path
+ 'directory.png' ;
75 if ( mimeTypes
. images
. some ( function ( e
) { return entry
. filePath
. endsWith ( e
); })) return sanitize ( basePath
+ '/' + entry
. filePath
);
76 if ( mimeTypes
. text
. some ( function ( e
) { return entry
. filePath
. endsWith ( e
); })) return path
+ 'text.png' ;
77 if ( mimeTypes
. pdf
. some ( function ( e
) { return entry
. filePath
. endsWith ( e
); })) return path
+ 'pdf.png' ;
78 if ( mimeTypes
. html
. some ( function ( e
) { return entry
. filePath
. endsWith ( e
); })) return path
+ 'html.png' ;
79 if ( mimeTypes
. video
. some ( function ( e
) { return entry
. filePath
. endsWith ( e
); })) return path
+ 'video.png' ;
81 return path
+ 'unknown.png' ;
84 // simple extension detection, does not work with double extension like .tar.gz
85 function getExtension ( entry
) {
86 if ( entry
. isFile
) return entry
. filePath
. slice ( entry
. filePath
. lastIndexOf ( '.' ) + 1 );
91 loadDirectory ( app
. path
);
95 superagent
. post ( '/api/logout' ). query ({ access_token : localStorage
. accessToken
}). end ( function ( error
) {
96 if ( error
) console
. error ( error
);
98 app
. session
. valid
= false ;
100 delete localStorage
. accessToken
;
104 function loadDirectory ( filePath
) {
107 filePath
= filePath
? sanitize ( filePath
) : '/' ;
109 superagent
. get ( '/api/files/' + encode ( filePath
)). query ({ access_token : localStorage
. accessToken
}). end ( function ( error
, result
) {
112 if ( result
&& result
. statusCode
=== 401 ) return logout ();
113 if ( error
) return console
. error ( error
);
115 result
. body
. entries
. sort ( function ( a
, b
) { return a
. isDirectory
&& b
. isFile
? - 1 : 1 ; });
116 app
. entries
= result
. body
. entries
. map ( function ( entry
) {
117 entry
. previewUrl
= getPreviewUrl ( entry
, filePath
);
118 entry
. extension
= getExtension ( entry
);
119 entry
. rename
= false ;
120 entry
. filePathNew
= entry
. filePath
;
124 app
. pathParts
= decode ( filePath
). split ( '/' ). filter ( function ( e
) { return !! e
; }). map ( function ( e
, i
, a
) {
127 link : '#' + sanitize ( '/' + a
. slice ( 0 , i
). join ( '/' ) + '/' + e
)
131 // update in case this was triggered from code
132 window
. location
. hash
= app
. path
;
136 function open ( row
, event
, column
) {
137 // ignore item open on row clicks if we are renaming this entry
138 if ( row
. rename
) return ;
140 var path
= sanitize ( app
. path
+ '/' + row
. filePath
);
142 if ( row
. isDirectory
) {
143 window
. location
. hash
= path
;
147 window
. open ( encode ( path
));
150 function uploadFiles ( files
) {
151 if (! files
|| ! files
. length
) return ;
153 app
. uploadStatus
. busy
= true ;
154 app
. uploadStatus
. count
= files
. length
;
155 app
. uploadStatus
. size
= 0 ;
156 app
. uploadStatus
. done
= 0 ;
157 app
. uploadStatus
. percentDone
= 0 ;
159 for ( var i
= 0 ; i
< files
. length
; ++ i
) {
160 app
. uploadStatus
. size
+= files
[ i
]. size
;
163 asyncForEach ( files
, function ( file
, callback
) {
164 var path
= encode ( sanitize ( app
. path
+ '/' + ( file
. webkitRelativePath
|| file
. name
)));
166 var formData
= new FormData ();
167 formData
. append ( 'file' , file
);
169 var finishedUploadSize
= app
. uploadStatus
. done
;
171 superagent
. post ( '/api/files' + path
)
172 . query ({ access_token : localStorage
. accessToken
})
174 . on ( 'progress' , function ( event
) {
175 // only handle upload events
176 if (!( event
. target
instanceof XMLHttpRequestUpload
)) return ;
178 app
. uploadStatus
. done
= finishedUploadSize
+ event
. loaded
;
179 var tmp
= Math
. round ( app
. uploadStatus
. done
/ app
. uploadStatus
. size
* 100 );
180 app
. uploadStatus
. percentDone
= tmp
> 100 ? 100 : tmp
;
181 }). end ( function ( error
, result
) {
182 if ( result
&& result
. statusCode
=== 401 ) return logout ();
183 if ( result
&& result
. statusCode
!== 201 ) return callback ( 'Error uploading file: ' , result
. statusCode
);
184 if ( error
) return callback ( error
);
188 }, function ( error
) {
189 if ( error
) console
. error ( error
);
191 app
. uploadStatus
. busy
= false ;
192 app
. uploadStatus
. count
= 0 ;
193 app
. uploadStatus
. size
= 0 ;
194 app
. uploadStatus
. done
= 0 ;
195 app
. uploadStatus
. percentDone
= 100 ;
201 function dragOver ( event
) {
202 event
. stopPropagation ();
203 event
. preventDefault ();
204 event
. dataTransfer
. dropEffect
= 'copy' ;
207 function drop ( event
) {
208 event
. stopPropagation ();
209 event
. preventDefault ();
211 if (! event
. dataTransfer
. items
[ 0 ]) return ;
213 // figure if a folder was dropped on a modern browser, in this case the first would have to be a directory
216 folderItem
= event
. dataTransfer
. items
[ 0 ]. webkitGetAsEntry ();
217 if ( folderItem
. isFile
) return uploadFiles ( event
. dataTransfer
. files
);
219 return uploadFiles ( event
. dataTransfer
. files
);
222 // if we got here we have a folder drop and a modern browser
223 // now traverse the folder tree and create a file list
224 app
. uploadStatus
. busy
= true ;
225 app
. uploadStatus
. uploadListCount
= 0 ;
228 function traverseFileTree ( item
, path
, callback
) {
231 item
. file ( function ( file
) {
233 ++ app
. uploadStatus
. uploadListCount
;
236 } else if ( item
. isDirectory
) {
237 // Get folder contents
238 var dirReader
= item
. createReader ();
239 dirReader
. readEntries ( function ( entries
) {
240 asyncForEach ( entries
, function ( entry
, callback
) {
241 traverseFileTree ( entry
, path
+ item
. name
+ '/' , callback
);
247 traverseFileTree ( folderItem
, '' , function ( error
) {
248 app
. uploadStatus
. busy
= false ;
249 app
. uploadStatus
. uploadListCount
= 0 ;
251 if ( error
) return console
. error ( error
);
253 uploadFiles ( fileList
);
262 origin : window
. location
. origin
,
275 folderListingEnabled : false ,
284 onLogin : function () {
287 that
. loginData
. busy
= true ;
289 superagent
. post ( '/api/login' ). send ({ username : that
. loginData
. username
, password : that
. loginData
. password
}). end ( function ( error
, result
) {
290 that
. loginData
. busy
= false ;
292 if ( error
&& ! result
) return that
.$ message
. error ( error
. message
);
293 if ( result
. statusCode
=== 401 ) return that
.$ message
. error ( 'Wrong username or password' );
295 getProfile ( result
. body
. accessToken
, function ( error
) {
296 if ( error
) return console
. error ( error
);
298 loadDirectory ( decode ( window
. location
. hash
. slice ( 1 )));
302 onOptionsMenu : function ( command
) {
303 if ( command
=== 'folderListing' ) {
304 superagent
. put ( '/api/settings' ). send ({ folderListingEnabled : this . folderListingEnabled
}). query ({ access_token : localStorage
. accessToken
}). end ( function ( error
) {
305 if ( error
) console
. error ( error
);
307 } else if ( command
=== 'about' ) {
309 title : 'About Surfer' ,
310 message : 'Surfer is a static file server written by <a href="https://cloudron.io" target="_blank">Cloudron</a>.<br/><br/>The source code is licensed under MIT and available <a href="https://git.cloudron.io/cloudron/surfer" target="_blank">here</a>.' ,
311 dangerouslyUseHTMLString : true ,
312 confirmButtonText : 'OK' ,
313 showCancelButton : false ,
316 }). then ( function () {}). catch ( function () {});
317 } else if ( command
=== 'logout' ) {
321 onDownload : function ( entry
) {
322 if ( entry
. isDirectory
) return ;
323 window
. location
. href
= encode ( '/api/files/' + sanitize ( this . path
+ '/' + entry
. filePath
)) + '?access_token=' + localStorage
. accessToken
;
325 onUpload : function () {
328 $( this .$ refs
. upload
). on ( 'change' , function () {
329 // detach event handler
330 $( that
.$ refs
. upload
). off ( 'change' );
331 uploadFiles ( that
.$ refs
. upload
. files
|| []);
334 // reset the form first to make the change handler retrigger even on the same file selected
335 this .$ refs
. upload
. value
= '' ;
336 this .$ refs
. upload
. click ();
338 onUploadFolder : function () {
341 $( this .$ refs
. uploadFolder
). on ( 'change' , function () {
342 // detach event handler
343 $( that
.$ refs
. uploadFolder
). off ( 'change' );
344 uploadFiles ( that
.$ refs
. uploadFolder
. files
|| []);
347 // reset the form first to make the change handler retrigger even on the same file selected
348 this .$ refs
. uploadFolder
. value
= '' ;
349 this .$ refs
. uploadFolder
. click ();
351 onDelete : function ( entry
) {
354 var title
= 'Really delete ' + ( entry
. isDirectory
? 'folder ' : '' ) + entry
. filePath
;
355 this .$ confirm ( '' , title
, { confirmButtonText : 'Yes' , cancelButtonText : 'No' }). then ( function () {
356 var path
= encode ( sanitize ( that
. path
+ '/' + entry
. filePath
));
358 superagent
. del ( '/api/files' + path
). query ({ access_token : localStorage
. accessToken
, recursive : true }). end ( function ( error
, result
) {
359 if ( result
&& result
. statusCode
=== 401 ) return logout ();
360 if ( result
&& result
. statusCode
!== 200 ) return that
.$ message
. error ( 'Error deleting file: ' + result
. statusCode
);
361 if ( error
) return that
.$ message
. error ( error
. message
);
365 }). catch ( function () {});
367 onRename : function ( entry
, scope
) {
368 if ( entry
. rename
) return entry
. rename
= false ;
372 Vue
. nextTick ( function () {
373 var elem
= document
. getElementById ( 'filePathRenameInputId-' + scope
.$ index
);
376 if ( typeof elem
. selectionStart
!= "undefined" ) {
377 elem
. selectionStart
= 0 ;
378 elem
. selectionEnd
= entry
. filePath
. lastIndexOf ( '.' );
382 onRenameEnd : function ( entry
) {
383 entry
. rename
= false ;
384 entry
. filePathNew
= entry
. filePath
;
386 onRenameSubmit : function ( entry
) {
389 entry
. rename
= false ;
391 if ( entry
. filePathNew
=== entry
. filePath
) return ;
393 var path
= encode ( sanitize ( this . path
+ '/' + entry
. filePath
));
394 var newFilePath
= sanitize ( this . path
+ '/' + entry
. filePathNew
);
396 superagent
. put ( '/api/files' + path
). query ({ access_token : localStorage
. accessToken
}). send ({ newFilePath : newFilePath
}). end ( function ( error
, result
) {
397 if ( result
&& result
. statusCode
=== 401 ) return logout ();
398 if ( result
&& result
. statusCode
!== 200 ) return that
.$ message
. error ( 'Error renaming file: ' + result
. statusCode
);
399 if ( error
) return that
.$ message
. error ( error
. message
);
401 entry
. filePath
= entry
. filePathNew
;
404 onNewFolder : function () {
407 var title
= 'Create New Folder' ;
408 this .$ prompt ( '' , title
, { confirmButtonText : 'Yes' , cancelButtonText : 'No' , inputPlaceholder : 'new foldername' }). then ( function ( data
) {
409 var path
= encode ( sanitize ( that
. path
+ '/' + data
. value
));
411 superagent
. post ( '/api/files' + path
). query ({ access_token : localStorage
. accessToken
, directory : true }). end ( function ( error
, result
) {
412 if ( result
&& result
. statusCode
=== 401 ) return logout ();
413 if ( result
&& result
. statusCode
=== 403 ) return that
.$ message
. error ( 'Folder name not allowed' );
414 if ( result
&& result
. statusCode
=== 409 ) return that
.$ message
. error ( 'Folder already exists' );
415 if ( result
&& result
. statusCode
!== 201 ) return that
.$ message
. error ( 'Error creating folder: ' + result
. statusCode
);
416 if ( error
) return that
.$ message
. error ( error
. message
);
420 }). catch ( function () {});
422 prettyDate : function ( row
, column
, cellValue
, index
) {
423 var date
= new Date ( cellValue
),
424 diff
= ((( new Date ()). getTime () - date
. getTime ()) / 1000 ),
425 day_diff
= Math
. floor ( diff
/ 86400 );
427 if ( isNaN ( day_diff
) || day_diff
< 0 )
430 return day_diff
=== 0 && (
431 diff
< 60 && 'just now' ||
432 diff
< 120 && '1 minute ago' ||
433 diff
< 3600 && Math
. floor ( diff
/ 60 ) + ' minutes ago' ||
434 diff
< 7200 && '1 hour ago' ||
435 diff
< 86400 && Math
. floor ( diff
/ 3600 ) + ' hours ago' ) ||
436 day_diff
=== 1 && 'Yesterday' ||
437 day_diff
< 7 && day_diff
+ ' days ago' ||
438 day_diff
< 31 && Math
. ceil ( day_diff
/ 7 ) + ' weeks ago' ||
439 day_diff
< 365 && Math
. round ( day_diff
/ 30 ) + ' months ago' ||
440 Math
. round ( day_diff
/ 365 ) + ' years ago' ;
442 prettyFileSize : function ( row
, column
, cellValue
, index
) {
443 return filesize ( cellValue
);
445 loadDirectory : loadDirectory
,
447 window
. location
. hash
= sanitize ( this . path
. split ( '/' ). slice ( 0 , - 1 ). filter ( function ( p
) { return !! p
; }). join ( '/' ));
455 getProfile ( localStorage
. accessToken
, function ( error
) {
456 if ( error
) return console
. error ( error
);
458 loadDirectory ( decode ( window
. location
. hash
. slice ( 1 )));
461 $( window
). on ( 'hashchange' , function () {
462 loadDirectory ( decode ( window
. location
. hash
. slice ( 1 )));