UNPKG

rich-filemanager

Version:

Highly customizable open-source file manager

1,371 lines (1,202 loc) 180 kB
/** * Rich Filemanager JS core * * filemanager.js * * @license MIT License * @author Jason Huck - Core Five Labs * @author Simon Georget <simon (at) linea21 (dot) com> * @author Pavel Solomienko <https://github.com/servocoder/> * @copyright Authors */ (function($) { $.richFilemanagerPlugin = function(element, pluginOptions) { /** * Plugin's default options */ var defaults = { baseUrl: '.', // relative path to the FM plugin folder configUrl: null, config: {}, // configuration options callbacks: { beforeCreateImageUrl: function (resourceObject, url) { return url; }, beforeCreatePreviewUrl: function (resourceObject, url) { return url; }, beforeSelectItem: function (resourceObject, url) { return url; }, afterSelectItem: function (resourceObject, url, contextWindow) {}, beforeSetRequestParams: function (requestMethod, requestParams) { return requestParams; }, beforeSendRequest: function (requestMethod, requestParams) { return true; } } }; /** * The reference the current instance of the object */ var fm = this; /** * Private properties accessible only from inside the plugin */ var $container = $(element), // reference to the jQuery version of DOM element the plugin is attached to $wrapper = $container.children('.fm-wrapper'), $header = $wrapper.find('.fm-header'), $uploader = $header.find('.fm-uploader'), $splitter = $wrapper.children('.fm-splitter'), $footer = $wrapper.children('.fm-footer'), $fileinfo = $splitter.children('.fm-fileinfo'), $filetree = $splitter.children('.fm-filetree'), $viewItemsWrapper = $fileinfo.find('.view-items-wrapper'), $previewWrapper = $fileinfo.find('.fm-preview-wrapper'), $viewItems = $viewItemsWrapper.find('.view-items'), $uploadButton = $uploader.children('.fm-upload'), config = null, // configuration options fileRoot = '/', // relative files root, may be changed with some query params apiConnector = null, // API connector URL to perform requests to server capabilities = [], // allowed actions to perform in FM configSortField = null, // items sort field name configSortOrder = null, // items sort order 'asc'/'desc' fmModel = null, // filemanager knockoutJS model langModel = null, // language model globalize = null, // formatting and parsing tool (numbers, dates, etc.) delayStack = null, // function execution delay manager /** variables to keep request options data **/ fullexpandedFolder = null, // path to be automatically expanded by filetree plugin /** service variables **/ _url_ = purl(), timeStart = new Date().getTime(); /** * This holds the merged default and user-provided options. * Plugin's properties will be available through this object like: * - fm.propertyName from inside the plugin * - element.data('richFilemanager').propertyName from outside the plugin, where "element" is the element the plugin is attached to; * @type {{}} */ // The plugin's final settings, contains the merged default and user-provided options (if any) fm.settings = $.extend(true, defaults, pluginOptions); /*-------------------------------------------------------------------------------------------------------------- Public methods Can be called like: - fm.methodName(arg1, arg2, ... argn) from inside the plugin - element.data('richFilemanager').publicMethod(arg1, arg2, ... argn) from outside the plugin, where "element" is the element the plugin is attached to --------------------------------------------------------------------------------------------------------------*/ fm.write = function(message, obj) { var log = alertify; var options = $.extend({}, { reset: true, delay: 5000, logMaxItems: 5, logPosition: 'bottom right', logContainerClass: 'fm-log', logMessageTemplate: null, parent: document.body, onClick: undefined, unique: false, type: 'log' }, obj); // display only one log for the specified 'logClass' if(options.logClass && options.unique && $('.fm-log').children('.' + options.logClass).length > 0) { return log; } if(options.reset) log.reset(); log.parent(options.parent); log.logDelay(options.delay); log.logMaxItems(options.logMaxItems); log.logPosition(options.logPosition); log.logContainerClass(options.logContainerClass); log.logMessageTemplate(options.logMessageTemplate); log[options.type](message, options.onClick); var logs = log.getLogs(); return logs[logs.length-1]; }; fm.error = function(message, options) { return fm.write(message, $.extend({}, { type: 'error', delay: 10000 }, options)); }; fm.warning = function(message, options) { return fm.write(message, $.extend({}, { type: 'warning', delay: 10000 }, options)); }; fm.success = function(message, options) { return fm.write(message, $.extend({}, { type: 'success', delay: 6000 }, options)); }; fm.alert = function(message) { alertify .reset() .dialogContainerClass('fm-popup') .alert(message); }; fm.confirm = function(obj) { alertify .reset() .dialogWidth(obj.width) .dialogPersistent(obj.persistent) .dialogContainerClass('fm-popup') .confirm(obj.message, obj.okBtn, obj.cancelBtn); }; fm.prompt = function(obj) { alertify .reset() .dialogWidth(obj.width) .dialogPersistent(obj.persistent) .dialogContainerClass('fm-popup') .theme(obj.template) .prompt(obj.message, obj.value || '', obj.okBtn, obj.cancelBtn); }; fm.dialog = function(obj) { alertify .reset() .dialogWidth(obj.width) .dialogPersistent(obj.persistent) .dialogContainerClass('fm-popup') .dialog(obj.message, obj.buttons); }; // Forces columns to fill the layout vertically. // Called on initial page load and on resize. fm.setDimensions = function() { var padding = $wrapper.outerHeight(true) - $wrapper.height(), newH = $(window).height() - $header.height() - $footer.height() - padding, newW = $splitter.width() - $splitter.children('.splitter-bar-vertical').outerWidth() - $filetree.outerWidth(); $splitter.height(newH); $fileinfo.width(newW); }; fm.console = function() { if(config.options.logger && arguments) { [].unshift.call(arguments, new Date().getTime()); console.log.apply(this, arguments); } }; // Reload currently open folder content fm.refreshFolder = function(applyTreeNode) { fmModel.loadPath(fmModel.currentPath(), applyTreeNode); }; // Load content of specified relative folder path fm.loadFolder = function(path, applyTreeNode) { path = '/' + trim(path, '/') + '/'; fmModel.loadPath(path, applyTreeNode); }; /*-------------------------------------------------------------------------------------------------------------- Private methods These methods can be called only from inside the plugin like: methodName(arg1, arg2, ... argn) --------------------------------------------------------------------------------------------------------------*/ /** * The "constructor" method that gets called when the object is created */ var construct = function() { var deferred = $.Deferred(); deferred .then(function() { return configure(); }) .then(function() { return localize(); }) .then(function(conf_d, conf_u) { return performInitialRequest(); }) .then(function() { return includeTemplates(); }) .then(function() { includeAssets(function() { initialize(); }); }); deferred.resolve(); }; var configure = function() { return $.when(loadConfigFile('default'), loadConfigFile('user')).done(function(confd, confu) { var config_default = confd[0]; var config_user = confu[0]; // remove version from user config file if (config_user !== undefined && config_user !== null) { delete config_user.version; } // merge default config and user config file config = $.extend({}, config_default, config_user); // setup apiConnector if(config.api.connectorUrl) { apiConnector = config.api.connectorUrl; } else { var connectorUrl = location.origin + location.pathname; var langConnector = 'connectors/' + config.api.lang + '/filemanager.' + config.api.lang; // for url like http://site.com/index.html if(getExtension(connectorUrl).length > 0) { connectorUrl = connectorUrl.substring(0, connectorUrl.lastIndexOf('/') + 1); } apiConnector = connectorUrl + langConnector; } }); }; // performs initial request to server to retrieve initial params var performInitialRequest = function () { return buildAjaxRequest('GET', { mode: 'initiate' }).done(function (response) { if (response.data) { var serverConfig = response.data.attributes.config; // configuration options retrieved from the server $.each(serverConfig, function (section, options) { $.each(options, function (param, value) { if (value === null) { return true; } if (config[section] === undefined) { config[section] = []; } config[section][param] = value; }); }); // Overrides client-side capabilities list to improve the compatibility for connectors. // If a connector doesn't support a new feature, the plugin will mask this feature to avoid error. if (serverConfig.options && serverConfig.options.capabilities) { config.options.capabilities = serverConfig.options.capabilities; } } }).fail(function (xhr) { fm.error('Unable to perform initial request to server.'); handleAjaxError(xhr); }).then(function (response) { if (response.errors) { return $.Deferred().reject(); } }); }; // localize messages based on configuration or URL value var localize = function() { langModel = new LangModel(); return $.ajax() .then(function() { var urlLangCode = _url_.param('langCode'); if(urlLangCode) { // try to load lang file based on langCode in query params return file_exists(langModel.buildLangFileUrl(urlLangCode)) .done(function() { langModel.setLang(urlLangCode); }) .fail(function() { setTimeout(function() { fm.error('Given language file (' + langModel.buildLangFileUrl(urlLangCode) + ') does not exist!'); }, 500); }); } else { langModel.setLang(config.language.default); } }) .then(function() { // append query param to prevent caching var langFileUrl = langModel.buildLangFileUrl(langModel.getLang()) + '?_=' + new Date().getTime(); return $.ajax({ type: 'GET', url: langFileUrl, dataType: 'json' }).done(function(jsonTrans) { langModel.setTranslations(jsonTrans); }); }) .then(function() { // trim language code to first 2 chars var lang = langModel.getLang().substr(0, 2), baseUrl = fm.settings.baseUrl; return $.when( $.get(baseUrl + '/libs/cldrjs/cldr-dates/' + lang + '/ca-gregorian.json'), $.get(baseUrl + '/libs/cldrjs/cldr-numbers/' + lang + '/numbers.json'), $.get(baseUrl + '/libs/cldrjs/cldr-core/supplemental/likelySubtags.json'), $.get(baseUrl + '/libs/cldrjs/cldr-core/supplemental/timeData.json'), $.get(baseUrl + '/libs/cldrjs/cldr-core/supplemental/weekData.json') ).fail(function () { fm.error('CLDR files for "' + lang + '" language do not exist!'); }).then(function () { // Normalize $.get results, we only need the JSON, not the request statuses. return [].slice.apply(arguments, [0]).map(function (result) { return result[0]; }); }).then(Globalize.load).then(function () { globalize = Globalize(lang); }); }); }; var includeTemplates = function() { return $.when(loadTemplate('upload-container'), loadTemplate('upload-item')).done(function(uc, ui) { var tmpl_upload_container = uc[0]; var tmpl_upload_item = ui[0]; $wrapper .append(tmpl_upload_container) .append(tmpl_upload_item); }); }; var includeAssets = function(callback) { var primary = [], secondary = []; // theme defined in configuration file primary.push('/themes/' + config.options.theme + '/styles/theme.css'); if(config.viewer.image.lazyLoad) { primary.push('/libs/lazyload/dist/lazyload.min.js'); } if(config.customScrollbar.enabled) { primary.push('/libs/custom-scrollbar-plugin/jquery.mCustomScrollbar.min.css'); primary.push('/libs/custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js'); } // add callback on loaded assets and inject primary ones primary.push(callback); loadAssets(primary); // Loading CodeMirror if enabled for online edition if(config.editor.enabled) { var editorTheme = config.editor.theme; if (editorTheme && editorTheme !== 'default') { secondary.push('/libs/CodeMirror/theme/' + editorTheme + '.css'); } secondary.push('/libs/CodeMirror/lib/codemirror.css'); secondary.push('/libs/CodeMirror/lib/codemirror.js'); secondary.push('/libs/CodeMirror/addon/selection/active-line.js'); secondary.push('/libs/CodeMirror/addon/display/fullscreen.css'); secondary.push('/libs/CodeMirror/addon/display/fullscreen.js'); } // Load Markdown-it, if enabled. For .md to HTML rendering: if(config.viewer.markdownRenderer.enabled) { secondary.push('/src/css/fm-markdown.css'); secondary.push('/libs/markdown-it/markdown-it.min.js'); secondary.push('/libs/markdown-it/default.min.css'); secondary.push('/libs/markdown-it/highlight.min.js'); secondary.push('/libs/markdown-it/markdown-it-footnote.min.js'); secondary.push('/libs/markdown-it/markdown-it-replace-link.min.js'); } if(!config.options.browseOnly) { // Loading jquery file upload library secondary.push('/src/js/libs-fileupload.js'); if(config.upload.multiple) { secondary.push('/libs/jQuery-File-Upload/css/dropzone.css'); } } if(secondary.length) { loadAssets(secondary); } }; var initialize = function () { delayStack = new DelayStack(); // reads capabilities from config files if exists else apply default settings capabilities = config.options.capabilities || ['upload', 'select', 'download', 'rename', 'copy', 'move', 'delete', 'extract', 'createFolder']; // If the server is in read only mode, set the GUI to browseOnly: if (config.security.readOnly) { config.options.browseOnly = true; } // Set default upload parameter if (!config.upload.paramName) { config.upload.paramName = 'files'; } // defines sort params var chunks = []; if(config.options.fileSorting) { chunks = config.options.fileSorting.toLowerCase().split('_'); } configSortField = chunks[0] || 'name'; configSortOrder = chunks[1] || 'asc'; // changes files root to restrict the view to a given folder var exclusiveFolder = _url_.param('exclusiveFolder'); if(exclusiveFolder) { fileRoot = '/' + exclusiveFolder + '/'; fileRoot = normalizePath(fileRoot); } // get folder that should be expanded after filemanager is loaded var expandedFolder = _url_.param('expandedFolder'); if(expandedFolder) { fullexpandedFolder = fileRoot + expandedFolder + '/'; fullexpandedFolder = normalizePath(fullexpandedFolder); } // Activates knockout.js fmModel = new FmModel(); ko.applyBindings(fmModel); fmModel.itemsModel.initiateLazyLoad(); fmModel.filterModel.setName(_url_.param('filter')); ko.bindingHandlers.toggleNodeVisibility = { init: function (element, valueAccessor) { var node = valueAccessor(); $(element).toggle(node.isExpanded()); }, update: function (element, valueAccessor) { var node = valueAccessor(); if(node.isSliding() === false) { return false; } if(node.isExpanded() === false) { $(element).slideDown(config.filetree.expandSpeed, function() { node.isSliding(false); node.isExpanded(true); }); } if(node.isExpanded() === true) { $(element).slideUp(config.filetree.expandSpeed, function() { node.isSliding(false); node.isExpanded(false); }); } } }; ko.bindingHandlers.draggableView = { init: function(element, valueAccessor, allBindingsAccessor) { fmModel.ddModel.makeDraggable(valueAccessor(), element); } }; ko.bindingHandlers.droppableView = { init: function(element, valueAccessor, allBindingsAccessor) { fmModel.ddModel.makeDroppable(valueAccessor(), element); } }; ko.bindingHandlers.draggableTree = { init: function(element, valueAccessor, allBindingsAccessor) { fmModel.ddModel.makeDraggable(valueAccessor(), element); } }; ko.bindingHandlers.droppableTree = { init: function(element, valueAccessor, allBindingsAccessor) { fmModel.ddModel.makeDroppable(valueAccessor(), element); } }; $wrapper.mousewheel(function(e) { if (!fmModel.ddModel.dragHelper) { return true; } var $panes, $obstacle = null; if (config.customScrollbar.enabled) { $panes = $([$viewItemsWrapper[0], $filetree[0]]); } else { $panes = $splitter.children('.splitter-pane'); } $panes.each(function(i) { var $pane = $(this), top = $pane.offset().top, left = $pane.offset().left; if ((e.offsetY >= top && e.offsetY <= top + $pane.height()) && (e.offsetX >= left && e.offsetX <= left + $pane.width())) { $obstacle = $pane; return false; } }); // no one appropriate obstacle is overlapped if ($obstacle === null) { return false; } if (config.customScrollbar.enabled) { var $scrollBar = $obstacle.find('.mCSB_scrollTools_vertical'), directionSign = (e.deltaY === 1) ? '+' : '-'; if ($scrollBar.is(':visible')) { $obstacle.mCustomScrollbar('scrollTo', [directionSign + '=250', 0], { scrollInertia: 500, scrollEasing: 'easeOut', callbacks: true }); } } else { if ($obstacle[0].scrollHeight > $obstacle[0].clientHeight) { var scrollPosition = $obstacle.scrollTop(); var scrollOffset = scrollPosition - (200 * e.deltaY); fmModel.ddModel.isScrolling = true; scrollOffset = (scrollOffset < 0) ? 0 : scrollOffset; $obstacle.stop().animate({scrollTop: scrollOffset}, 100, 'linear', function() { fmModel.ddModel.isScrolling = false; fmModel.ddModel.isScrolled = true; }); } } }); $viewItems.selectable({ filter: 'li:not(.directory-parent), tbody > tr:not(.directory-parent)', cancel: '.directory-parent, thead', disabled: !config.manager.selection.enabled, appendTo: $viewItems, start: function(event, ui) { clearSelection(); fmModel.itemsModel.isSelecting(true); }, stop: function(event, ui) { fmModel.itemsModel.isSelecting(false); }, selected: function(event, ui) { var koItem = ko.dataFor(ui.selected); koItem.selected(true); }, unselected: function(event, ui) { var koItem = ko.dataFor(ui.unselected); koItem.selected(false); } }); $fileinfo.contextMenu({ selector: '.view-items', zIndex: 10, // wrap options with "build" allows to get item element build: function ($triggerElement, e) { var contextMenuItems = { createFolder: { name: lg('create_folder'), className: 'create-folder' }, paste: { name: lg('clipboard_paste'), className: 'paste', disabled: function (key, options) { return fmModel.clipboardModel.isEmpty(); } } }; if (!fmModel.clipboardModel.enabled() || config.options.browseOnly === true) { delete contextMenuItems.paste; } if (!hasCapability('createFolder') || config.options.browseOnly === true) { delete contextMenuItems.createFolder; } // prevent the creation of context menu if ($.isEmptyObject(contextMenuItems)) { return false; } return { appendTo: '.fm-container', items: contextMenuItems, reposition: false, callback: function(itemKey, options) { switch(itemKey) { case 'createFolder': fmModel.headerModel.createFolder(); break; case 'paste': fmModel.clipboardModel.paste(); break; } } } } }); if(config.extras.extra_js) { for(var i=0; i<config.extras.extra_js.length; i++) { $.ajax({ type: 'GET', url: config.extras.extra_js[i], dataType: 'script', async: config.extras.extra_js_async }); } } // adding a close button triggering callback function if CKEditorCleanUpFuncNum passed if(_url_.param('CKEditorCleanUpFuncNum')) { fmModel.headerModel.closeButton(true); fmModel.headerModel.closeButtonOnClick = function() { parent.CKEDITOR.tools.callFunction(_url_.param('CKEditorCleanUpFuncNum')); }; } prepareFileTree(); setupUploader(); fmModel.treeModel.loadDataNode(fmModel.treeModel.rootNode, true, true); // Loading CustomScrollbar if enabled if(config.customScrollbar.enabled) { $filetree.mCustomScrollbar({ theme: config.customScrollbar.theme, scrollButtons: { enable: config.customScrollbar.button }, advanced: { autoExpandHorizontalScroll: true, updateOnContentResize: true }, callbacks: { onScrollStart: function() { fmModel.ddModel.isScrolling = true; }, onScroll: function() { fmModel.ddModel.isScrolling = false; } }, axis: 'yx' }); $previewWrapper.mCustomScrollbar({ theme: config.customScrollbar.theme, scrollButtons: { enable: config.customScrollbar.button }, advanced: { autoExpandHorizontalScroll: true, updateOnContentResize: true, updateOnSelectorChange: '.fm-preview-viewer' } }); $viewItemsWrapper.mCustomScrollbar({ theme: config.customScrollbar.theme, scrollButtons: { enable: config.customScrollbar.button }, advanced: { autoExpandHorizontalScroll:true, updateOnContentResize: true, updateOnSelectorChange: '.grid, .list' }, callbacks: { onScrollStart: function() { if (!fmModel.itemsModel.continiousSelection()) { this.yStartPosition = this.mcs.top; this.yStartTime = (new Date()).getTime(); } fmModel.ddModel.isScrolling = true; }, onScroll: function() { fmModel.ddModel.isScrolling = false; fmModel.ddModel.isScrolled = true; }, whileScrolling: function() { if (config.manager.selection.enabled) { // would prefer to get scroll position from [onScrollStart], // but the [onScroll] should fire first, which happens with a big lag var timeDiff = (new Date()).getTime() - this.yStartTime; // check if selection lasso has not been dropped while scrolling if (!fmModel.itemsModel.continiousSelection() && timeDiff > 400) { this.yStartPosition = this.mcs.top; } // set flag if selection lasso is active if (fmModel.itemsModel.isSelecting()) { fmModel.itemsModel.continiousSelection(true); } var yIncrement = Math.abs(this.mcs.top) - Math.abs(this.yStartPosition); $viewItems.selectable('repositionCssHelper', yIncrement, 0); } if (fmModel.itemsModel.lazyLoad) { fmModel.itemsModel.lazyLoad.handleScroll(); // use throttle } } }, axis: 'y', alwaysShowScrollbar: 0 }); } // add useragent string to html element for IE 10/11 detection var doc = document.documentElement; doc.setAttribute('data-useragent', navigator.userAgent); if(config.options.logger) { var timeEnd = new Date().getTime(); var time = timeEnd - timeStart; console.log('Total execution time : ' + time + ' ms'); } var $loading = $container.find('.fm-loading-wrap'); // remove loading screen div $loading.fadeOut(800, function() { fm.setDimensions(); }); fm.setDimensions(); }; /** * Language model * * @constructor */ var LangModel = function() { var currentLang = null, translationsHash = {}, translationsPath = fm.settings.baseUrl + '/languages/'; this.buildLangFileUrl = function(code) { return translationsPath + code + '.json'; }; this.setLang = function(code) { currentLang = code; }; this.getLang = function() { return currentLang; }; this.setTranslations = function(json) { translationsHash = json; }; this.getTranslations = function() { return translationsHash; }; this.translate = function(key) { return translationsHash[key]; }; }; /** * DelayStack * Delays execution of functions that is passed as argument * * @constructor */ var DelayStack = function() { var hash = {}, delay_stack = this; this.push = function(name, callback, ms) { delay_stack.removeTimer(name); hash[name] = setTimeout(callback, ms); }; this.getTimer = function(name) { return hash[name]; }; this.removeTimer = function(name) { if (hash[name]) { clearTimeout(hash[name]); delete hash[name]; } }; }; /** * Knockout general model * * @constructor */ var FmModel = function() { var model = this; this.config = ko.observable(config); this.loadingView = ko.observable(true); this.previewFile = ko.observable(false); this.viewMode = ko.observable(config.manager.defaultViewMode); this.currentPath = ko.observable(fileRoot); this.browseOnly = ko.observable(config.options.browseOnly); this.previewModel = ko.observable(null); this.currentLang = langModel.getLang(); this.lg = langModel.getTranslations(); this.previewFile.subscribe(function (enabled) { if (!enabled) { // close editor upon disabling preview model.previewModel.closeEditor(); // update content of descriptive panel if (model.itemsModel.descriptivePanel.rdo().id === model.previewModel.rdo().id) { model.itemsModel.descriptivePanel.render(model.previewModel.viewer.content()); } } }); this.isCapable = function(capability) { return hasCapability(capability); }; this.loadPath = function (targetPath, applyTreeNode) { var targetNode, folderLoader = new FolderAjaxLoader(targetPath); if (applyTreeNode) { targetNode = fmModel.treeModel.findByParam('id', targetPath); } if (targetNode) { folderLoader.setPreloader(fmModel.treeModel.getPreloader(targetNode)) } folderLoader .setPreloader(model.itemsModel.getPreloader()) .setDataHandler(function (resourceObjects, targetPath) { if (targetNode) { fmModel.treeModel.addNodes(resourceObjects, targetNode, true); } model.itemsModel.addItems(resourceObjects, targetPath, true); model.searchModel.clearInput(); }) .load(function () { return readFolder(targetPath); }); }; this.addElements = function (resourceObjects, targetPath, reset) { // handle tree nodes var targetNode = model.treeModel.findByParam('id', targetPath); if (targetNode) { model.treeModel.addNodes(resourceObjects, targetNode, reset); } // handle view objects if (model.currentPath() === targetPath) { model.itemsModel.addItems(resourceObjects, targetPath, reset); } }; this.removeElement = function (resourceObject) { // handle tree nodes var treeNode = model.treeModel.findByParam('id', resourceObject.id); if (treeNode) { treeNode.remove(); } // handle view objects var viewItem = model.itemsModel.findByParam('id', resourceObject.id); if (viewItem) { viewItem.remove(); } }; // fetch selected view items OR tree nodes this.fetchSelectedItems = function(instanceName) { var selectedNodes, selectedItems; if (instanceName === ItemObject.name) { return model.itemsModel.getSelected(); } if (instanceName === TreeNodeModel.name) { return model.treeModel.getSelected(); } if (!instanceName) { selectedNodes = model.treeModel.getSelected(); selectedItems = model.itemsModel.getSelected(); return (selectedItems.length > 0) ? selectedItems : selectedNodes; } throw new Error('Unknown item type.'); }; // fetch resource objects out of the selected items this.fetchSelectedObjects = function(item) { var objects = []; $.each(model.fetchSelectedItems(item.constructor.name), function(i, itemObject) { objects.push(itemObject.rdo); }); return objects; }; // check whether view item can be opened based on the event and configuration options function isItemOpenable(event) { // selecting with Ctrl key if(config.manager.selection.enabled && config.manager.selection.useCtrlKey && event.ctrlKey === true) { return false; } // single clicked while expected dblclick if(config.manager.dblClickOpen && event.type === 'click') { return false; } return true; } /** * PanelLoader Interface * * @constructor */ var PanelLoader = function() { this.beforeLoad = function(path) {}; this.afterLoad = function(path, response) {}; }; /** * Preview model * * @constructor */ var PreviewModel = function() { var preview_model = this, clipboard = null; this.rdo = ko.observable({}); // computed resource data object this.cdo = ko.observable({}); this.viewer = { type: ko.observable('default'), isEditable: ko.observable(false), url: ko.observable(null), pureUrl: ko.observable(null), options: ko.observable({}), content: ko.observable(null), codeMirror: ko.observable(null) }; this.renderer = new RenderModel(); this.editor = new EditorModel(); this.rdo.subscribe(function (resourceObject) { preview_model.cdo({ isFolder: (resourceObject.type === 'folder'), sizeFormatted: formatBytes(resourceObject.attributes.size), createdFormatted: formatTimestamp(resourceObject.attributes.created), modifiedFormatted: formatTimestamp(resourceObject.attributes.modified), extension: (resourceObject.type === 'file') ? getExtension(resourceObject.id) : null, dimensions: resourceObject.attributes.width ? resourceObject.attributes.width + 'x' + resourceObject.attributes.height : null }); }); this.editor.content.subscribe(function (content) { if (preview_model.editor.isInteractive()) { // instantly render changes of editor content preview_model.renderer.render(content); } }); this.applyObject = function(resourceObject) { if (clipboard) { clipboard.destroy(); } model.previewFile(false); var filename = resourceObject.attributes.name, editorObject = { interactive: false }, viewerObject = { type: 'default', url: null, options: {} }; preview_model.rdo(resourceObject); if(isImageFile(filename)) { viewerObject.type = 'image'; viewerObject.url = createImageUrl(resourceObject, false, true); } if(isAudioFile(filename) && config.viewer.audio.enabled === true) { viewerObject.type = 'audio'; viewerObject.url = createPreviewUrl(resourceObject, true); } if(isVideoFile(filename) && config.viewer.video.enabled === true) { viewerObject.type = 'video'; viewerObject.url = createPreviewUrl(resourceObject, true); viewerObject.options = { width: config.viewer.video.playerWidth, height: config.viewer.video.playerHeight }; } // if(isOnlyOfficeFile(filename) && config.viewer.onlyoffice.enabled === true) { // viewerObject.type = 'onlyoffice'; // var connectorUrl = config.viewer.onlyoffice.connectorUrl || fm.settings.baseUrl + '/connectors/php/onlyoffice/editor.php'; // viewerObject.url = connectorUrl + '?path=' + encodeURIComponent(resourceObject.attributes.path); // viewerObject.options = { // width: config.viewer.onlyoffice.editorWidth, // height: config.viewer.onlyoffice.editorHeight // }; // } if(isOpenDocFile(filename) && config.viewer.opendoc.enabled === true) { viewerObject.type = 'opendoc'; viewerObject.url = fm.settings.baseUrl + '/libs/ViewerJS/index.html#' + createPreviewUrl(resourceObject, true); viewerObject.options = { width: config.viewer.opendoc.readerWidth, height: config.viewer.opendoc.readerHeight }; } if(isGoogleDocsFile(filename) && config.viewer.google.enabled === true) { viewerObject.type = 'google'; viewerObject.url = 'https://docs.google.com/viewer?url=' + encodeURIComponent(createPreviewUrl(resourceObject, false)) + '&embedded=true'; viewerObject.options = { width: config.viewer.google.readerWidth, height: config.viewer.google.readerHeight }; } if (isIFrameFile(filename) && config.viewer.iframe.enabled === true) { viewerObject.type = 'iframe'; viewerObject.url = createPreviewUrl(resourceObject, true); viewerObject.options = { width: config.viewer.iframe.readerWidth, height: config.viewer.iframe.readerHeight }; } if ((isCodeMirrorFile(filename) && config.viewer.codeMirrorRenderer.enabled === true) || (isMarkdownFile(filename) && config.viewer.markdownRenderer.enabled === true) ) { viewerObject.type = 'renderer'; viewerObject.options = { is_writable: resourceObject.attributes.writable }; preview_model.renderer.setRenderer(resourceObject); editorObject.interactive = preview_model.renderer.renderer().interactive; } preview_model.viewer.type(viewerObject.type); preview_model.viewer.url(viewerObject.url); preview_model.viewer.options(viewerObject.options); preview_model.viewer.pureUrl(createCopyUrl(resourceObject)); preview_model.viewer.isEditable(isEditableFile(filename) && config.editor.enabled === true); preview_model.editor.isInteractive(editorObject.interactive); if (viewerObject.type === 'renderer' || preview_model.viewer.isEditable()) { previewItem(resourceObject).then(function(content) { preview_model.viewer.content(content); model.previewFile(true); }); } else { model.previewFile(true); } }; this.afterRender = function() { preview_model.renderer.render(preview_model.viewer.content()); var copyBtnEl = $previewWrapper.find('.btn-copy-url')[0]; clipboard = new Clipboard(copyBtnEl); clipboard.on('success', function(e) { fm.success(lg('copied')); }); }; this.initiateEditor = function(elements) { var textarea = $previewWrapper.find('.fm-cm-editor-content')[0]; preview_model.editor.createInstance(preview_model.cdo().extension, textarea, { readOnly: false, styleActiveLine: true }); }; // fires specific action by clicking toolbar buttons in detail view this.bindToolbar = function(action) { if (isObjectCapable(preview_model.rdo(), action)) { performAction(action, {}, preview_model.rdo()); } }; this.previewIconClass = ko.pureComputed(function() { var cssClass = [], extraClass = ['ico']; if(preview_model.viewer.type() === 'default' || !preview_model.viewer.url()) { cssClass.push('grid-icon'); if(this.cdo().isFolder === true) { cssClass.push('ico_folder'); extraClass.push('folder'); if(!this.rdo().attributes.readable) { extraClass.push('lock'); } } else { cssClass.push('ico_file'); if(this.rdo().attributes.readable) { extraClass.push('ext', this.cdo().extension); } else { extraClass.push('file', 'lock'); } } cssClass.push(extraClass.join('_')); } return cssClass.join(' '); }, this); this.closePreview = function() { model.previewFile(false); }; this.editFile = function() { var content = preview_model.viewer.content(); preview_model.renderer.render(content); preview_model.editor.render(content); }; this.saveFile = function() { saveItem(preview_model.rdo()); }; this.closeEditor = function() { preview_model.editor.enabled(false); // re-render viewer content preview_model.renderer.render(preview_model.viewer.content()); }; this.buttonVisibility = function(action) { switch(action) { case 'select': return (isObjectCapable(preview_model.rdo(), action) && hasContext()); case 'move': case 'rename': case 'delete': case 'download': return (isObjectCapable(preview_model.rdo(), action)); } }; }; var TreeModel = function() { var tree_model = this; this.selectedNode = ko.observable(null); var rootNode = new TreeNodeModel({attributes: {}}); rootNode.id = fileRoot; rootNode.level = ko.observable(-1); this.rootNode = rootNode; function expandFolderDefault(parentNode) { if (fullexpandedFolder !== null) { if(!parentNode) { parentNode = tree_model.rootNode; } // looking for node that starts with specified path var node = tree_model.findByFilter(function (node) { return (fullexpandedFolder.indexOf(node.id) === 0); }, parentNode); if (node) { config.filetree.expandSpeed = 10; tree_model.loadDataNode(node, false, true); } else { fullexpandedFolder = null; config.filetree.expandSpeed = 200; tree_model.setItemsFromNode(parentNode); } } } this.mapNodes = function(filter, contextNode) { if (!contextNode) { contextNode = tree_model.rootNode; } // don't apply callback function to the filetree root node if (!contextNode.isRoot()) { filter.call(this, contextNode); } var nodes = contextNode.children(); if (!nodes || nodes.length === 0) { return null; } for (var i = 0, l = nodes.length; i < l; i++) { filter.call(this, nodes[i]); tree_model.findByFilter(filter, nodes[i]); } }; this.findByParam = function(key, value, contextNode) { if(!contextNode) { contextNode = tree_model.rootNode; if(contextNode[key] === value) { return contextNode; } } var nodes = contextNode.children(); if(!nodes || nodes.length === 0) { return null; } for (var i = 0, l = nodes.length; i < l; i++) { if (nodes[i][key] === value) { return nodes[i]; } var result = tree_model.findByParam(key, value, nodes[i]); if(result) return result; } return null; }; this.findByFilter = function(filter, contextNode) { if(!contextNode) { contextNode = tree_model.rootNode; if(filter(contextNode)) { return contextNode; } } var nodes = contextNode.children(); if(!nodes || nodes.length === 0) { return null; } for (var i = 0, l = nodes.length; i < l; i++) { if(filter(nodes[i])) { return nodes[i]; } var result = tree_model.findByFilter(filter, nodes[i]); if(result) return result; } return null; }; this.getSelected = function() { var selectedItems = []; if (tree_model.selectedNode()) { selectedItems.push(tree_model.selectedNode()); } return selectedItems; }; this.loadDataNode = function(targetNode, populateItems, refresh) { var targetPath = targetNode.id; var folderLoader = new FolderAjaxLoader(targetPath); folderLoader .setPreloader(tree_model.getPreloader(targetNode)) .setDataHandler(function (resourceObjects, targetPath) { tree_model.addNodes(resourceObjects, targetNode, refresh); }); if (populateItems) { folderLoader .setPreloader(model.itemsModel.getPreloader()) .setDataHandler(function (resourceObjects, targetPath) { model.itemsModel.addItems(resourceObjects, targetPath, refresh); model.searchModel.clearInput(); }); } folderLoader.load(function() { return readFolder(targetPath); }); }; this.getPreloader = function(targetNode) { var preloader = function() {}; preloader.prototype = Object.create(PanelLoader); preloader.prototype.beforeLoad = function(path) { if(!targetNode.isRoot()) { targetNode.isLoaded(false); } }; preloader.prototype.afterLoad = function(path, response) { if(!targetNode.isRoot()) { targetNode.isLoaded(true); tree_model.expandNode(targetNode); } expandFolderDefault(targetNode); }; return new preloader(); }; this.createNode = function(resourceObject) { var node = new TreeNodeModel(resourceObject); fmModel.filterModel.filterItem(node); return node; }; this.createNodes = function(resourceObjects) { var nodes = []; $.each(resourceObjects, function(i, resourceObject) { nodes.push(tree_model.createNode(resourceObject));