rich-filemanager
Version:
Highly customizable open-source file manager
1,371 lines (1,202 loc) • 180 kB
JavaScript
/**
* 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));