diff --git a/build/ifm.js b/build/ifm.js index f5520dc..579f20c 100644 --- a/build/ifm.js +++ b/build/ifm.js @@ -1 +1,1243 @@ -src/ifm.js \ No newline at end of file +/** + * IFM constructor + * + * @param object params - object with some configuration values, currently you only can set the api url + */ +function IFM( params ) { + var self = this; // reference to ourself, because "this" does not work within callbacks + + // set the backend for the application + params = params || {}; + self.api = params.api || window.location.pathname; + + this.editor = null; // global ace editor + this.fileChanged = false; // flag for check if file was changed already + this.currentDir = ""; // this is the global variable for the current directory; it is used for AJAX requests + this.rootElement = ""; + + /** + * Shows a bootstrap modal + * + * @param string content - content of the modal + * @param object options - options for the modal + */ + this.showModal = function( content, options = {} ) { + var modal = $( document.createElement( 'div' ) ) + .addClass( "modal fade" ) + .attr( 'id', 'ifmmodal' ) + .attr( 'role', 'dialog' ); + var modalDialog = $( document.createElement( 'div' ) ) + .addClass( "modal-dialog" ) + .attr( 'role', 'document' ); + if( options.large == true ) modalDialog.addClass( 'modal-lg' ); + var modalContent = $(document.createElement('div')) + .addClass("modal-content") + .append( content ); + modalDialog.append( modalContent ); + modal.append( modalDialog ); + $( document.body ).append( modal ); + modal.on('hide.bs.modal', function () { $(this).remove(); }); + modal.on('shown.bs.modal', function () { + var formElements = $(this).find('input, button'); + if( formElements.length > 0 ) { + formElements.first().focus(); + } + }); + modal.modal('show'); + }; + + /** + * Hides a bootstrap modal + */ + this.hideModal = function() { + $('#ifmmodal').modal('hide'); + }; + + /** + * Reloads the file table + */ + this.refreshFileTable = function () { + var id = self.generateGuid(); + self.task_add( "Refresh", id ); + $.ajax({ + url: self.api, + type: "POST", + data: { + api: "getFiles", + dir: self.currentDir + }, + dataType: "json", + success: self.rebuildFileTable, + error: function( response ) { self.showMessage( "General error occured: No or broken response", "e" ); }, + complete: function() { self.task_done( id ); } + }); + }; + + /** + * Rebuilds the file table with fetched items + * + * @param object data - object with items + */ + this.rebuildFileTable = function( data ) { + data.forEach( function( item ) { + item.guid = self.generateGuid(); + item.linkname = ( item.name == ".." ) ? "[ up ]" : item.name; + item.download = {}; + item.download.name = ( item.name == ".." ) ? "." : item.name; + item.download.allowed = self.config.download; + item.download.currentDir = self.currentDir; + if( ! self.config.chmod ) + item.readonly = "readonly"; + if( self.config.edit || self.config.rename || self.config.delete || self.config.extract || self.config.copymove ) { + item.ftbuttons = true; + item.button = []; + } + if( item.type == "dir" ) { + item.download.action = "zipnload"; + item.download.icon = "icon icon-download-cloud"; + item.rowclasses = "isDir"; + } else { + item.download.action = "download"; + item.download.icon = "icon icon-download"; + if( item.icon.indexOf( 'file-image' ) !== -1 && self.config.isDocroot ) + item.tooltip = 'data-toggle="tooltip" title=""'; + if( item.name.toLowerCase().substr(-4) == ".zip" ) + item.eaction = "extract"; + else + item.eaction = "edit"; + if( self.config.edit && item.name.toLowerCase().substr(-4) != ".zip" ) + item.button.push({ + action: "edit", + icon: "icon icon-pencil", + title: "edit" + }); + else + item.button.push({ + action: "extract", + icon: "icon icon-archive", + title: "extract" + }); + } + if( ! self.inArray( item.name, [".", ".."] ) ) { + if( self.config.copymove ) + item.button.push({ + action: "copymove", + icon: "icon icon-folder-open-empty", + title: "copy/move" + }); + if( self.config.rename ) + item.button.push({ + action: "rename", + icon: "icon icon-terminal", + title: "rename" + }); + if( self.config.delete ) + item.button.push({ + action: "delete", + icon: "icon icon-trash", + title: "delete" + }); + } + }); + var newTBody = Mustache.render( self.templates.filetable, { items: data, config: self.config } ); + $( "#filetable tbody" ).remove(); + $( "#filetable" ).append( $(newTBody) ); + $( '.clickable-row' ).click( function( event ) { + if( event.ctrlKey ) { + $( this ).toggleClass( 'selectedItem' ); + } + }); + $( 'a[data-toggle="tooltip"]' ).tooltip({ + animated: 'fade', + placement: 'right', + html: true + }); + $( 'a.ifmitem' ).each( function() { + if( $(this).data( "type" ) == "dir" ) { + $(this).on( 'click', function( e ) { + e.stopPropagation(); + self.changeDirectory( $(this).parent().parent().data( 'filename' ) ); + return false; + }); + } else { + if( self.config.isDocroot ) + $(this).attr( "href", self.pathCombine( self.currentDir, $(this).parent().parent().data( 'filename' ) ) ); + else + $(this).on( 'click', function() { + $( '#d_' + this.id ).submit(); + return false; + }); + } + }); + $( 'a[name="start_download"]' ).on( 'click', function(e) { + e.stopPropagation(); + $( '#d_' + $(this).data( 'guid' ) ).submit(); + return false; + }); + $( 'input[name="newpermissions"]' ).on( 'keypress', function( e ) { + if( e.key == "Enter" ) { + e.stopPropagation(); + self.changePermissions( $( this ).data( 'filename' ), $( this ).val() ); + return false; + } + }); + $( 'a[name^="do-"]' ).on( 'click', function() { + var action = this.name.substr( this.name.indexOf( '-' ) + 1 ); + switch( action ) { + case "rename": + self.showRenameFileDialog( $(this).data( 'name' ) ); + break; + case "extract": + self.showExtractFileDialog( $(this).data( 'name' ) ); + break; + case "edit": + self.editFile( $(this).data( 'name' ) ); + break; + case "delete": + self.showDeleteFileDialog( $(this).data( 'name' ) ); + break; + case "copymove": + self.showCopyMoveDialog( $(this).data( 'name' ) ); + break; + } + }); + + }; + + /** + * Changes the current directory + * + * @param string newdir - target directory + * @param object options - options for changing the directory + */ + this.changeDirectory = function( newdir, options={} ) { + config = { absolute: false, pushState: true }; + jQuery.extend( config, options ); + if( ! config.absolute ) newdir = self.pathCombine( self.currentDir, newdir ); + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "getRealpath", + dir: newdir + }), + dataType: "json", + success: function( data ) { + self.currentDir = data.realpath; + self.refreshFileTable(); + $( "#currentDir" ).val( self.currentDir ); + if( config.pushState ) history.pushState( { dir: self.currentDir }, self.currentDir, "#"+self.currentDir ); + }, + error: function() { self.showMessage( "General error occured: No or broken response", "e" ); } + }); + }; + + /** + * Shows a file, either a new file or an existing + */ + this.showFileDialog = function () { + var filename = arguments.length > 0 ? arguments[0] : "newfile.txt"; + var content = arguments.length > 1 ? arguments[1] : ""; + self.showModal( Mustache.render( self.templates.file, { filename: filename } ), { large: true } ); + var form = $('#formFile'); + form.find('input[name="filename"]').on( 'keypress', self.preventEnter ); + form.find('#buttonSave').on( 'click', function() { + self.saveFile( form.find('input[name=filename]').val(), self.editor.getValue() ); + self.hideModal(); + return false; + }); + form.find('#buttonSaveNotClose').on( 'click', function() { + self.saveFile( form.find('input[name=filename]').val(), self.editor.getValue() ); + return false; + }); + form.find('#buttonClose').on( 'click', function() { + self.hideModal(); + return false; + }); + form.find('#editoroptions').popover({ + html: true, + title: function() { return $('#editoroptions-head').html(); }, + content: function() { + var content = $('#editoroptions-content').clone() + var aceSession = self.editor.getSession(); + content.removeClass( 'hide' ); + content.find( '#editor-wordwrap' ) + .prop( 'checked', ( aceSession.getOption( 'wrap' ) == 'off' ? false : true ) ) + .on( 'change', function() { self.editor.setOption( 'wrap', $( this ).is( ':checked' ) ); }); + content.find( '#editor-softtabs' ) + .prop( 'checked', aceSession.getOption( 'useSoftTabs' ) ) + .on( 'change', function() { self.editor.setOption( 'useSoftTabs', $( this ).is( ':checked' ) ); }); + content.find( '#editor-tabsize' ) + .val( aceSession.getOption( 'tabSize' ) ) + .on( 'keydown', function( e ) { if( e.key == 'Enter' ) { self.editor.setOption( 'tabSize', $( this ).val() ); } }); + return content; + } + }); + form.on( 'remove', function () { self.editor = null; self.fileChanged = false; }); + // Start ACE + self.editor = ace.edit("content"); + self.editor.$blockScrolling = 'Infinity'; + self.editor.getSession().setValue(content); + self.editor.focus(); + self.editor.on("change", function() { self.fileChanged = true; }); + // word wrap checkbox + $('#aceWordWrap').on( 'change', function (event) { + self.editor.getSession().setUseWrapMode( $(this).is(':checked') ); + }); + }; + + /** + * Saves a file + */ + this.saveFile = function( filename, content ) { + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "saveFile", + dir: self.currentDir, + filename: filename, + content: content + }), + dataType: "json", + success: function( data ) { + if( data.status == "OK" ) { + self.showMessage( "File successfully edited/created.", "s" ); + self.refreshFileTable(); + } else self.showMessage( "File could not be edited/created:" + data.message, "e" ); + }, + error: function() { self.showMessage( "General error occured", "e" ); } + }); + self.fileChanged = false; + }; + + /** + * Edit a file + * + * @params string name - name of the file + */ + this.editFile = function( name ) { + $.ajax({ + url: self.api, + type: "POST", + dataType: "json", + data: ({ + api: "getContent", + dir: self.currentDir, + filename: name + }), + success: function( data ) { + if( data.status == "OK" && data.data.content != null ) { + self.showFileDialog( data.data.filename, data.data.content ); + } + else if( data.status == "OK" && data.data.content == null ) { + self.showMessage( "The content of this file cannot be fetched.", "e" ); + } + else self.showMessage( "Error: "+data.message, "e" ); + }, + error: function() { self.showMessage( "This file can not be displayed or edited.", "e" ); } + }); + }; + + /** + * Shows the create directory dialog + */ + this.showCreateDirDialog = function() { + self.showModal( self.templates.createdir ); + var form = $( '#formCreateDir' ); + form.find( 'input[name=dirname]' ).on( 'keypress', self.preventEnter ); + form.find( '#buttonSave' ).on( 'click', function() { + self.createDir( form.find( 'input[name=dirname] ').val() ); + self.hideModal(); + return false; + }); + form.find( '#buttonCancel' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Create a directory + */ + this.createDir = function( dirname ) { + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "createDir", + dir: self.currentDir, + dirname: dirname + }), + dataType: "json", + success: function( data ){ + if( data.status == "OK" ) { + self.showMessage( "Directory sucessfully created.", "s" ); + self.refreshFileTable(); + } + else { + self.showMessage( "Directory could not be created: "+data.message, "e" ); + } + }, + error: function() { self.showMessage( "General error occured.", "e" ); } + }); + }; + + + /** + * Shows the delete file dialog + * + * @param string name - name of the file + */ + this.showDeleteFileDialog = function( filename ) { + self.showModal( Mustache.render( self.templates.deletefile, { filename: name } ) ); + var form = $( '#formDeleteFile' ); + form.find( '#buttonYes' ).on( 'click', function() { + self.deleteFile( self.JSEncode( filename ) ); + self.hideModal(); + return false; + }); + form.find( '#buttonNo' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Deletes a file + * + * @params string name - name of the file + */ + this.deleteFile = function( filename ) { + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "delete", + dir: self.currentDir, + filename: filename + }), + dataType: "json", + success: function(data) { + if(data.status == "OK") { + self.showMessage("File successfully deleted", "s"); + self.refreshFileTable(); + } else self.showMessage("File could not be deleted", "e"); + }, + error: function() { self.showMessage("General error occured", "e"); } + }); + }; + + /** + * Show the rename file dialog + * + * @params string name - name of the file + */ + this.showRenameFileDialog = function( filename ) { + self.showModal( Mustache.render( self.templates.renamefile, { filename: filename } ) ); + var form = $( '#formRenameFile' ); + form.find( 'input[name=newname]' ).on( 'keypress', self.preventEnter ); + form.find( '#buttonRename' ).on( 'click', function() { + self.renameFile( filename, form.find( 'input[name=newname]' ).val() ); + self.hideModal(); + return false; + }); + form.find( '#buttonCancel' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Renames a file + * + * @params string name - name of the file + */ + this.renameFile = function( filename, newname ) { + $.ajax({ + url: ifm.api, + type: "POST", + data: ({ + api: "rename", + dir: ifm.currentDir, + filename: filename, + newname: newname + }), + dataType: "json", + success: function(data) { + if(data.status == "OK") { + ifm.showMessage("File successfully renamed", "s"); + ifm.refreshFileTable(); + } else ifm.showMessage("File could not be renamed: "+data.message, "e"); + }, + error: function() { ifm.showMessage("General error occured", "e"); } + }); + }; + + /** + * Show the copy/move dialog + * + * @params string name - name of the file + */ + this.showCopyMoveDialog = function( name ) { + self.showModal( self.templates.copymove ); + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "getFolderTree", + dir: self.currentDir + }), + dataType: "json", + success: function( data ) { + $( '#copyMoveTree' ).treeview( { data: data, levels: 0, expandIcon: "icon icon-folder-empty", collapseIcon: "icon icon-folder-open-empty" } ); + }, + error: function() { self.hideModal(); self.showMessage( "Error while fetching the folder tree.", "e" ) } + }); + $( '#copyButton' ).on( 'click', function() { + self.copyMove( name, $('#copyMoveTree .node-selected').data('path'), 'copy' ); + self.hideModal(); + return false; + }); + $( '#moveButton' ).on( 'click', function() { + self.copyMove( name, $('#copyMoveTree .node-selected').data('path'), 'move' ); + self.hideModal(); + return false; + }); + $( '#cancelButton' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Copy or moves a file + * + * @params string name - name of the file + */ + this.copyMove = function( source, destination, action ) { + var id = self.generateGuid(); + self.task_add( action.charAt(0).toUpperCase() + action.slice(1) + " " + source + " to " + destination, id ); + $.ajax({ + url: self.api, + type: "POST", + data: { + dir: self.currentDir, + api: "copyMove", + action: action, + filename: source, + destination: destination + }, + dataType: "json", + success: function(data) { + if( data.status == "OK" ) { + self.showMessage( data.message, "s" ); + } else { + self.showMessage( data.message, "e" ); + } + self.refreshFileTable(); + }, + error: function() { + self.showMessage( "General error occured.", "e" ); + }, + complete: function() { + self.task_done( id ); + } + }); + }; + + /** + * Shows the extract file dialog + * + * @param string name - name of the file + */ + this.showExtractFileDialog = function( filename ) { + var targetDirSuggestion = ''; + if( filename.lastIndexOf( '.' ) > 1 ) + targetDirSuggestion = filename.substr( 0, filename.lastIndexOf( '.' ) ); + else targetDirSuggestion = filename; + self.showModal( Mustache.render( self.templates.extractfile, { filename: filename, destination: targetDirSuggestion } ) ); + var form = $('#formExtractFile'); + form.find('#buttonExtract').on( 'click', function() { + var t = form.find('input[name=extractTargetLocation]:checked').val(); + if( t == "custom" ) t = form.find('#extractCustomLocation').val(); + self.extractFile( self.JSEncode( filename ), t ); + self.hideModal(); + return false; + }); + form.find('#buttonCancel').on( 'click', function() { + self.hideModal(); + return false; + }); + form.find('#extractCustomLocation').on( 'click', function(e) { + $(e.target).prev().children().first().prop( 'checked', true ); + }); + }; + + /** + * Extracts a file + * + * @param string filename - name of the file + * @param string destination - name of the target directory + */ + this.extractFile = function( filename, destination ) { + $.ajax({ + url: self.api, + type: "POST", + data: { + api: "extract", + dir: self.currentDir, + filename: filename, + targetdir: destination + }, + dataType: "json", + success: function( data ) { + if( data.status == "OK" ) { + self.showMessage( "File successfully extracted", "s" ); + self.refreshFileTable(); + } else self.showMessage( "File could not be extracted. Error: " + data.message, "e" ); + }, + error: function() { self.showMessage( "General error occured", "e" ); } + }); + }; + + /** + * Shows the upload file dialog + */ + this.showUploadFileDialog = function() { + self.showModal( self.templates.uploadfile ); + var form = $('#formUploadFile'); + form.find( 'input[name=newfilename]' ).on( 'keypress', self.preventEnter ); + form.find( '#buttonUpload' ).on( 'click', function() { + self.uploadFile(); + self.hideModal(); + return false; + }); + form.find( '#buttonCancel' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Uploads a file + */ + this.uploadFile = function() { + var ufile = document.getElementById( 'ufile' ).files[0]; + var data = new FormData(); + var newfilename = $("#formUploadFile input[name^=newfilename]").val(); + data.append('api', 'upload'); + data.append('dir', self.currentDir); + data.append('file', ufile); + data.append('newfilename', newfilename); + var id = self.generateGuid(); + $.ajax({ + url: self.api, + type: "POST", + data: data, + processData: false, + contentType: false, + dataType: "json", + xhr: function(){ + var xhr = $.ajaxSettings.xhr() ; + xhr.upload.onprogress = function(evt){ self.task_update(evt.loaded/evt.total*100,id); } ; + xhr.upload.onload = function(){ console.log('Uploading '+newfilename+' done.') } ; + return xhr ; + }, + success: function(data) { + if(data.status == "OK") { + self.showMessage("File successfully uploaded", "s"); + if(data.cd == self.currentDir) self.refreshFileTable(); + } else self.showMessage("File could not be uploaded: "+data.message, "e"); + }, + error: function() { self.showMessage("General error occured", "e"); }, + complete: function() { self.task_done(id); } + }); + self.task_add("Upload "+ufile.name, id); + }; + + /** + * Change the permissions of a file + * + * @params object e - event object + * @params string name - name of the file + */ + this.changePermissions = function( filename, newperms) { + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "changePermissions", + dir: self.currentDir, + filename: filename, + chmod: newperms + }), + dataType: "json", + success: function( data ){ + if( data.status == "OK" ) { + self.showMessage( "Permissions successfully changed.", "s" ); + self.refreshFileTable(); + } + else { + self.showMessage( "Permissions could not be changed: "+data.message, "e"); + } + }, + error: function() { self.showMessage("General error occured.", "e"); } + }); + }; + + /** + * Show the remote upload dialog + */ + this.showRemoteUploadDialog = function() { + self.showModal( self.templates.remoteupload ); + var form = $('#formRemoteUpload'); + form.find( '#url' ) + .on( 'keypress', self.preventEnter ) + .on( 'change keyup', function() { + $("#filename").val($(this).val().substr($(this).val().lastIndexOf("/")+1)); + }); + form.find( '#filename' ) + .on( 'keypress', self.preventEnter ) + .on( 'keyup', function() { $("#url").off( 'change keyup' ); }); + form.find( '#buttonUpload' ).on( 'click', function() { + self.remoteUpload(); + self.hideModal(); + return false; + }); + form.find( '#buttonCancel' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Remote uploads a file + */ + this.remoteUpload = function() { + var filename = $("#formRemoteUpload #filename").val(); + var id = ifm.generateGuid(); + $.ajax({ + url: ifm.api, + type: "POST", + data: ({ + api: "remoteUpload", + dir: ifm.currentDir, + filename: filename, + method: $("#formRemoteUpload input[name=method]:checked").val(), + url: encodeURI($("#url").val()) + }), + dataType: "json", + success: function(data) { + if(data.status == "OK") { + ifm.showMessage("File successfully uploaded", "s"); + ifm.refreshFileTable(); + } else ifm.showMessage("File could not be uploaded:
"+data.message, "e"); + }, + error: function() { ifm.showMessage("General error occured", "e"); }, + complete: function() { ifm.task_done(id); } + }); + ifm.task_add("Remote upload: "+filename, id); + }; + + /** + * Shows the ajax request dialog + */ + this.showAjaxRequestDialog = function() { + self.showModal( self.templates.ajaxrequest ); + var form = $('#formAjaxRequest'); + form.find( '#ajaxurl' ).on( 'keypress', self.preventEnter ); + form.find( '#buttonRequest' ).on( 'click', function() { + self.ajaxRequest(); + return false; + }); + form.find( '#buttonClose' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Performs an ajax request + */ + this.ajaxRequest = function() { + $.ajax({ + url : $("#ajaxurl").val(), + cache : false, + data : $('#ajaxdata').val().replace(/\n/g,"&"), + type : $('#ajaxrequest input[name=arMethod]:checked').val(), + success : function(response) { $("#ajaxresponse").text(response); }, + error : function(e) { self.showMessage("Error: "+e, "e"); console.log(e); } + }); + }; + + /** + * Shows the delete dialog for multiple files + */ + this.showMultiDeleteDialog = function() { + self.showModal( Mustache.render( self.templates.multidelete, { count: $('#filetable tr.selectedItem').length } ) ); + var form = $('#formDeleteFiles'); + form.find( '#buttonYes' ).on( 'click', function() { + self.multiDelete(); + self.hideModal(); + return false; + }); + form.find( '#buttonNo' ).on( 'click', function() { + self.hideModal(); + return false; + }); + }; + + /** + * Deletes multiple files + */ + this.multiDelete = function() { + var elements = $('#filetable tr.selectedItem'); + var filenames = []; + for(var i=0;typeof(elements[i])!='undefined';filenames.push(elements[i++].getAttribute('data-filename'))); + $.ajax({ + url: self.api, + type: "POST", + data: ({ + api: "multidelete", + dir: self.currentDir, + filenames: filenames + }), + dataType: "json", + success: function(data) { + if(data.status == "OK") { + if(data.errflag == 1) + ifm.showMessage("All files successfully deleted.", "s"); + else if(data.errflag == 0) + ifm.showMessage("Some files successfully deleted. "+data.message); + else + ifm.showMessage("Files could not be deleted. "+data.message, "e"); + ifm.refreshFileTable(); + } else ifm.showMessage("Files could not be deleted:
"+data.message, "e"); + }, + error: function() { ifm.showMessage("General error occured", "e"); } + }); + }; + + // -------------------- + // helper functions + // -------------------- + + /** + * Shows a notification + * + * @param string m - message text + * @param string t - message type (e: error, s: success) + */ + this.showMessage = function(m, t) { + var msgType = (t == "e")?"danger":(t == "s")?"success":"info"; + var element = ( self.config.inline ) ? self.rootElement : "body"; + $.notify( + { message: m }, + { type: msgType, delay: 5000, mouse_over: 'pause', offset: { x: 15, y: 65 }, element: element } + ); + }; + + /** + * Combines two path components + * + * @param string a - component 1 + * @param string b - component 2 + */ + this.pathCombine = function(a, b) { + if(a == "" && b == "") return ""; + if(b[0] == "/") b = b.substring(1); + if(a == "") return b; + if(a[a.length-1] == "/") a = a.substring(0, a.length-1); + if(b == "") return a; + return a+"/"+b; + }; + + /** + * Prevents a user to submit a form via clicking enter + * + * @param object e - click event + */ + this.preventEnter = function(e) { + if( e.keyCode == 13 ) return false; + else return true; + } + + /** + * Checks if an element is part of an array + * + * @param obj needle - search item + * @param array haystack - array to search + */ + this.inArray = function(needle, haystack) { + for(var i = 0; i < haystack.length; i++) { if(haystack[i] == needle) return true; } return false; + }; + + /** + * Adds a task to the taskbar. + * + * @param string name - description of the task + * @param string id - identifier for the task + */ + this.task_add = function( name, id ) { + if( ! document.getElementById( "waitqueue" ) ) { + $( document.body ).prepend( '
' ); + } + $( "#waitqueue" ).prepend('\ +
\ +
\ +
\ +
\ + '+name+'\ +
\ +
\ +
\ + '); + }; + + /** + * Removes a task from the taskbar + * + * @param string id - task identifier + */ + this.task_done = function(id) { + $("#"+id).remove(); + if($("#waitqueue>div").length == 0) { + $("#waitqueue").remove(); + } + }; + + /** + * Updates a task + * + * @param integer progress - percentage of status + * @param string id - task identifier + */ + this.task_update = function(progress, id) { + $('#'+id+' .progress-bar').css('width', progress+'%').attr('aria-valuenow', progress); + }; + + /** + * Highlights an item in the file table + * + * @param object param - either an element id or a jQuery object + */ + this.highlightItem = function( param ) { + var highlight = function( el ) { + el.addClass( 'highlightedItem' ).siblings().removeClass( 'highlightedItem' ); + el.find( 'a' ).first().focus(); + if( ! self.isElementInViewport( el ) ) { + var scrollOffset = 0; + if( param=="prev" ) + scrollOffset = el.offset().top - ( window.innerHeight || document.documentElement.clientHeight ) + el.height() + 15; + else + scrollOffset = el.offset().top - 55; + $('html, body').animate( { scrollTop: scrollOffset }, 200 ); + } + }; + if( param.jquery ) { + highlight( param ); + } else { + var highlightedItem = $('.highlightedItem'); + if( ! highlightedItem.length ) { + highlight( $('#filetable tbody tr:first-child') ); + } else { + var newItem = ( param=="next" ? highlightedItem.next() : highlightedItem.prev() ); + + if( newItem.is( 'tr' ) ) { + highlight( newItem ); + } + } + } + }; + + /** + * Checks if an element is within the viewport + * + * @param object el - element object + */ + this.isElementInViewport = function (el) { + if (typeof jQuery === "function" && el instanceof jQuery) { + el = el[0]; + } + var rect = el.getBoundingClientRect(); + return ( + rect.top >= 60 && + rect.left >= 0 && + rect.bottom <= ( (window.innerHeight || document.documentElement.clientHeight) ) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + + /** + * Generates a GUID + */ + this.generateGuid = function() { + var result, i, j; + result = ''; + for(j=0; j<20; j++) { + i = Math.floor(Math.random()*16).toString(16).toUpperCase(); + result = result + i; + } + return result; + }; + + /** + * Logs a message if debug mode is on + * + * @param string m - message text + */ + this.log = function( m ) { + if( self.config.debug ) { + console.log( "IFM (debug): " + m ); + } + }; + + /** + * Encodes a string for use within javascript + * + * @param string s - encoding string + */ + this.JSEncode = function(s) { + return s.replace(/'/g, '\\x27').replace(/"/g, '\\x22'); + }; + + /** + * Handles the javascript pop states + * + * @param object event - event object + */ + this.historyPopstateHandler = function(event) { + var dir = ""; + if( event.state && event.state.dir ) dir = event.state.dir; + self.changeDirectory( dir, { pushState: false, absolute: true } ); + }; + + /** + * Handles keystrokes + * + * @param object e - event object + */ + this.handleKeystrokes = function( e ) { + // bind 'del' key + if( $(e.target).closest('input')[0] || $(e.target).closest('textarea')[0] ) { + return; + } + + switch( e.key ) { + case 'Delete': + if( self.config.delete ) { + if( $('#filetable tr.selectedItem').length > 0 ) { + e.preventDefault(); + self.showMultiDeleteDialog(); + } else { + var item = $('.highlightedItem'); + if( item.length ) + self.showDeleteFileDialog( item.data( 'filename' ) ); + } + } + break; + case 'e': + if( self.config.edit ) { + var item = $('.highlightedItem'); + if( item.length && ! item.hasClass( 'isDir' ) ) { + e.preventDefault(); + var action = item.data( 'eaction' ); + switch( action ) { + case 'extract': + self.showExtractFileDialog( item.data( 'filename' ) ); + break; + case 'edit': + self.editFile( item.data( 'filename' ) ); + } + } + } + break; + case 'g': + e.preventDefault(); + $('#currentDir').focus(); + break; + case 'r': + e.preventDefault(); + self.refreshFileTable(); + break; + case 'u': + if( self.config.upload ) { + e.preventDefault(); + self.showUploadFileDialog(); + } + break; + case 'o': + if( self.config.remoteupload ) { + e.preventDefault(); + self.showRemoteUploadDialog(); + } + break; + case 'a': + if( self.config.ajaxrequest ) { + e.preventDefault(); + self.showAjaxRequestDialog(); + } + break; + case 'F': + if( self.config.createfile ) { + e.preventDefault(); + self.showFileDialog(); + } + break; + case 'D': + if( self.config.createdir ) { + e.preventDefault(); + self.showCreateDirDialog(); + } + break; + case 'h': + case 'ArrowLeft': + e.preventDefault(); + self.changeDirectory( '..' ); + break; + case 'l': + case 'ArrowRight': + e.preventDefault(); + var item = $('.highlightedItem'); + if( item.hasClass('isDir') ) + self.changeDirectory( item.data( 'filename' ) ); + break; + case 'j': + case 'ArrowDown': + e.preventDefault(); + self.highlightItem('next'); + break; + case 'k': + case 'ArrowUp': + e.preventDefault(); + self.highlightItem('prev'); + break; + case 'Escape': + if( $(':focus').is( '.clickable-row td:first-child a:first-child' ) && $('.highlightedItem').length ) { + e.preventDefault(); + $('.highlightedItem').removeClass( 'highlightedItem' ); + } + break; + case ' ': // todo: make it work only when noting other is focused + case 'Enter': + if( $(':focus').is( '.clickable-row td:first-child a:first-child' ) ) { + var trParent = $(':focus').parent().parent(); + if( e.key == 'Enter' && trParent.hasClass( 'isDir' ) ) { + e.preventDefault(); + e.stopPropagation(); + self.changeDirectory( trParent.data( 'filename' ) ); + } else if( e.key == ' ' && ! trParent.is( ':first-child' ) ) { + e.preventDefault(); + e.stopPropagation(); + var item = $('.highlightedItem'); + if( item.is( 'tr' ) ) + item.toggleClass( 'selectedItem' ); + } + } + break; + } + } + + /** + * Initializes the application + */ + this.initLoadConfig = function() { + $.ajax({ + url: self.api, + type: "POST", + data: { + api: "getConfig" + }, + dataType: "json", + success: function(d) { + self.config = d; + self.log( "configuration loaded" ); + self.initLoadTemplates(); + }, + error: function() { + throw new Error( "IFM: could not load configuration" ); + } + }); + }; + + this.initLoadTemplates = function() { + // load the templates from the backend + $.ajax({ + url: self.api, + type: "POST", + data: { + api: "getTemplates" + }, + dataType: "json", + success: function(d) { + self.templates = d; + self.log( "templates loaded" ); + self.initApplication(); + }, + error: function() { + throw new Error( "IFM: could not load templates" ); + } + }); + }; + + this.initApplication = function() { + self.rootElement.html( + Mustache.render( + self.templates.app, + { + showpath: "/", + config: self.config, + ftbuttons: function(){ + return ( self.config.edit || self.config.rename || self.config.delete || self.config.zipnload || self.config.extract ); + } + } + ) + ); + // bind static buttons + $("#refresh").click(function(){ + self.refreshFileTable(); + }); + $("#createFile").click(function(){ + self.showFileDialog(); + }); + $("#createDir").click(function(){ + self.showCreateDirDialog(); + }); + $("#upload").click(function(){ + self.showUploadFileDialog(); + }); + $('#currentDir').on( 'keypress', function (event) { + if( event.keyCode == 13 ) { + event.preventDefault(); + self.changeDirectory( $(this).val(), { absolute: true } ); + } + }); + $('#buttonRemoteUpload').on( 'click', function() { + self.showRemoteUploadDialog(); + return false; + }); + $('#buttonAjaxRequest').on( 'click', function() { + self.showAjaxRequestDialog(); + return false; + }); + // handle keystrokes + $(document).on( 'keydown', self.handleKeystrokes ); + // handle history manipulation + window.onpopstate = self.historyPopstateHandler; + // load initial file table + if( window.location.hash ) { + self.changeDirectory( window.location.hash.substring( 1 ) ); + } else { + this.refreshFileTable(); + } + }; + + this.init = function( id ) { + self.rootElement = $('#'+id); + this.initLoadConfig(); + }; +} + +var ifm = new IFM(); +ifm.init( "ifm" );