2021-07-27 14:46:32 +02:00

2019 lines
50 KiB
JavaScript

(function ($, Drupal) {
'use strict';
/**
* @file
* Defines Imce File Manager.
*/
/**
* Global container.
*/
var imce = window.imce = {
// Configuration options
conf: {},
// Locally stored data
local: {},
// Events
events: {},
// Shortcuts
shortcuts: {fm: {}, tree: {}, content: {}},
// Toolbar buttons
toolbarButtons: {},
// Folder tree
tree: {},
// Currently selected items
selection: [],
// Message queue
messageQueue: [],
// Sort handlers
sorters: {}
};
/**
* Initiate imce on document ready.
*/
$(document).ready(function () {
var settings = window.drupalSettings;
var conf = settings && settings.imce;
var body = document.body;
if (conf && !imce.activeFolder && $(body).hasClass('imce-page')) {
if (!conf.ajax_url) {
conf.ajax_url = Drupal.url(settings.path.currentPath);
}
imce.init(conf, body);
}
});
/**
* Initialize imce.
*/
imce.init = function (conf, parentEl) {
// Set conf
conf = $.extend(imce.conf, conf);
if (!conf.ajax_url || !conf.folders || !conf.root_url) {
return;
}
imce.parentEl = parentEl;
// Get stored data
try {
$.extend(imce.local, JSON.parse(localStorage.getItem('imce.local')));
}
catch (err) {
imce.delayError(err);
}
// Init
imce.trigger('preinit');
imce.checkIntegration();
imce.initUI();
imce.initTree();
// Add shortcuts
imce.addShortcut('ENTER', imce.eTreeEnter, 'tree');
imce.addShortcut('UP', imce.eTreeUp, 'tree');
imce.addShortcut('DOWN', imce.eTreeDown, 'tree');
imce.addShortcut('LEFT', imce.eTreeLR, 'tree');
imce.addShortcut('RIGHT', imce.eTreeLR, 'tree');
imce.addShortcut('ENTER', imce.eContentEnter, 'content');
imce.addShortcut('UP', imce.eContentArrow, 'content');
imce.addShortcut('DOWN', imce.eContentArrow, 'content');
imce.addShortcut('LEFT', imce.eContentArrow, 'content');
imce.addShortcut('RIGHT', imce.eContentArrow, 'content');
imce.addShortcut('Ctrl+A', imce.eContentCtrlA, 'content');
// Add refresh button
imce.addTbb('refresh', {
title: Drupal.t('Refresh'),
permission: 'browse_files|browse_subfolders',
handler: imce.eFmRefresh,
shortcut: 'F5',
icon: 'refresh'
});
// Register default activeFolder handler
imce.bind('activateFolder', imce.defaultActivateFolder);
// Trigger init handlers
imce.trigger('init');
// Create sendto toolbar button if needed.
imce.createSendtoTbb();
// Add the file manager to the page
parentEl.appendChild(imce.fmEl);
// Set window events
$(window).bind('beforeunload', imce.eWinBeforeunload).bind('resize', imce.eWinResize);
imce.eWinResize();
// Content focus
imce.contentEl.focus();
// Set initial messages
imce.ajaxProcessMessages(conf);
// Open active path
var path = conf.active_path;
var Folder = path && imce.addFolder(path);
if (!Folder) {
for (path in conf.folders) {
if (Folder = imce.getFolder(path)) {
break;
}
}
}
if (Folder) {
Folder.open();
}
// Triger postinit
imce.trigger('postinit');
};
/**
* Init UI elements.
*/
imce.initUI = function () {
var el = imce.fmEl;
var createEl = imce.createEl;
if (el) {
return el;
}
el = imce.fmEl = createEl('<div id="imce-fm"></div>');
el.onkeydown = imce.eFmKeydown;
el.tabIndex = 0;
// Toolbar
el.appendChild(imce.toolbarEl = createEl('<div id="imce-toolbar" aria-label="Operations" role="toolbar"></div>'));
// Body
el.appendChild(imce.bodyEl = createEl('<div id="imce-body"></div>'));
// Tree
el = imce.treeEl = createEl('<div id="imce-tree"></div>');
el.onkeydown = imce.eTreeKeydown;
el.onmousedown = imce.eTreeMousedown;
el.ontouchstart = imce.eTreeTouchstart;
el.tabIndex = 0;
imce.bodyEl.appendChild(el);
// Tree resizer
el = imce.treeResizerEl = createEl('<div id="imce-tree-resizer"></div>');
el.onmousedown = imce.eTreeResizerMousedown;
el.ontouchstart = imce.eTreeResizerTouchstart;
imce.bodyEl.appendChild(el);
// Content
el = imce.contentEl = createEl('<div id="imce-content"></div>');
if (imce.conf.thumbnail_grid_style) {
el.className = 'thumbnail-grid';
}
el.onmousedown = imce.eContentMousedown;
el.ontouchstart = imce.eContentTouchstart;
el.onkeydown = imce.eContentKeydown;
el.onscroll = imce.eContentScroll;
el.tabIndex = 0;
imce.bodyEl.appendChild(el);
// Content header
el = imce.contentHeaderEl = imce.createEl('<div class="imce-content-header"><div class="imce-item"><div class="imce-item-date" data-sort="date">' + Drupal.t('Date') + '</div><div class="imce-item-height" data-sort="height">' + Drupal.t('Height') + '</div><div class="imce-item-width" data-sort="width">' + Drupal.t('Width') + '</div><div class="imce-item-size" data-sort="size">' + Drupal.t('Size') + '</div><div class="imce-item-icon imce-ficon" data-sort="ext"></div><div class="imce-item-name" data-sort="name">' + Drupal.t('Name') + '</div></div></div>');
el.onclick = imce.eContentHeaderClick;
imce.contentEl.appendChild(el);
// Content status
el = imce.contentStatusEl = imce.createEl('<div class="imce-content-status"></div>');
imce.contentEl.appendChild(el);
// Body resizer
el = imce.bodyResizerEl = createEl('<div id="imce-body-resizer"></div>');
el.onmousedown = imce.eBodyResizerMousedown;
el.ontouchstart = imce.eBodyResizerTouchstart;
imce.fmEl.appendChild(el);
// Preview
imce.fmEl.appendChild(imce.previewEl = createEl('<div id="imce-preview"></div>'));
return el;
};
/**
* Init folder tree.
*/
imce.initTree = function () {
var path;
var folders = imce.getConf('folders');
// Create root
var scheme = imce.getConf('scheme');
var root = new imce.Folder(scheme ? scheme + '://' : '<' + Drupal.t('root') + '>');
root.setPath('.');
root.branchEl.className += ' root';
imce.treeEl.appendChild(root.branchEl);
// Create predefined folders in alphabetical order.
var paths = [];
for (path in folders) {
if (imce.owns(folders, path)) {
paths.push(path);
}
}
paths.sort();
for (var i = 0; path = paths[i]; i++) {
imce.addFolder(path, folders[path]);
}
};
/**
* Returns a folder by path.
*/
imce.getFolder = function (path) {
if (imce.owns(imce.tree, path)) {
return imce.tree[path];
}
};
/**
* Returns an item by path.
*/
imce.getItem = function (path) {
var Folder;
var parts = imce.splitPath(path);
if (parts) {
if (Folder = imce.getFolder(parts[0])) {
return Folder.getItem(parts[1]);
}
}
};
/**
* Adds a folder to the tree.
*/
imce.addFolder = function (path, conf) {
var parts;
var parent;
var Folder = imce.getFolder(path);
// Existing
if (Folder) {
if (conf) {
Folder.setConf(conf);
}
return Folder;
}
// New. Append to the parent.
if (parts = imce.splitPath(path)) {
if (parent = imce.addFolder(parts[0])) {
Folder = new imce.Folder(parts[1], conf);
parent.appendItem(Folder);
return Folder;
}
}
};
/**
* Add a toolbar button.
*/
imce.addTbb = function (id, opt) {
return imce.getTbb(id) || new imce.Tbb(id, opt);
};
/**
* Returns a toolbar button.
*/
imce.getTbb = function (id) {
return imce.toolbarButtons[id];
};
/**
* Returns a configuration option.
*/
imce.getConf = function (name, defaultValue) {
var value;
var conf = imce.conf;
if (!name) {
return conf;
}
value = conf[name];
return value == null ? defaultValue : value;
};
/**
* Returns a copy of selected items.
*/
imce.getSelection = function () {
return imce.selection.slice(0);
};
/**
* Counts selected items.
*/
imce.countSelection = function () {
return this.selection.length;
};
/**
* Returns the selected items grouped by parent folder and type.
*/
imce.groupSelection = function () {
return imce.groupItems(imce.selection);
};
/**
* Select all items in the active folder.
*/
imce.selectAll = function () {
var Folder = imce.activeFolder;
if (Folder) {
Folder.selectAll();
}
};
/**
* Deselects all items.
*/
imce.deselectAll = function () {
var i;
var arr = imce.getSelection();
for (i in arr) {
if (imce.owns(arr, i)) {
arr[i].deselect();
}
}
};
/**
* Returns last selected item.
*/
imce.getLastSelected = function () {
var arr = imce.selection;
var len = arr.length;
if (len) {
return arr[len - 1];
}
};
/**
* Adds an item to the selection.
*/
imce.selectItem = function (Item) {
if (!Item.selected) {
var arr = imce.selection;
var oldlen = arr.length;
arr.push(Item);
Item.setState('selected');
if (oldlen < 2) {
imce.updatePreview();
}
}
};
/**
* Removes an item from the selection.
*/
imce.deselectItem = function (Item) {
if (Item.selected) {
var arr = imce.selection;
var i = $.inArray(Item, arr);
Item.unsetState('selected');
if (i !== -1) {
arr.splice(i, 1);
if (arr.length < 2) {
imce.updatePreview();
}
}
}
};
/**
* Loads item uuids by ajax.
*/
imce.loadItemUuids = function (items, callback) {
var i;
var Item;
var missing = [];
var loaded = [];
for (i in items) {
Item = items[i];
if (Item && Item.isFile) {
if (Item.uuid) {
loaded.push(Item);
}
else {
missing.push(Item);
}
}
}
// All loaded
if (!missing.length) {
if (callback) {
callback(loaded);
}
return loaded;
}
// Load missing uuids
return imce.ajaxItems('uuid', missing, {
customComplete: function(xhr, status) {
var path;
var Item;
var response = this.response;
if (response && response.uuids) {
for (path in response.uuids) {
if (Item = imce.getItem(path)) {
Item.uuid = response.uuids[path];
loaded.push(Item);
}
}
}
if (callback) {
callback(loaded);
}
}
});
};
/**
* Checks external application integration by URL parameters.
*
* Ex-1: http://example.com/imce?sendto=HANDLER
* Creates a sendto operation that calls HANDLER(File, imceWin) of the parent window.
* Ex-2: http://example.com/imce?urlField=FIELD-ID
* Creates a sendto operation that fills the field in parent window with the selected file's URL.
* Ex-3: http://example.com/imce?oninit=HANDLER
* Calls HANDLER() with imce context when the UI is ready.
*/
imce.checkIntegration = function () {
var query = imce.getQuery();
var handler;
var urlField;
var parentWin = window.opener || window.parent;
if (imce.parentWin = parentWin) {
// Check sendto handler
if (handler = imce.resolveHandler(query.sendto, parentWin)) {
imce.sendtoHandler = handler;
}
// Check url field
else if (urlField = query.urlField) {
if (urlField = parentWin.document.getElementById(urlField)) {
imce.sendtoHandler = function (Item, win) {
try {
imce.parentWin.focus();
(imce.parentWin.jQuery||$)(urlField).val(Item.getUrl()).blur().change().focus();
}
catch (err) {
imce.delayError(err);
}
win.close();
};
}
}
// Check init handler
if (handler = imce.resolveHandler(query.oninit, parentWin)) {
imce.bind('init', handler);
}
// Store sendto type
if (imce.sendtoHandler && query.type) {
imce.sendtoType = query.type;
}
}
};
/**
* Creates the sendto toolbar button.
*/
imce.createSendtoTbb = function (title, desc) {
if (imce.sendtoHandler && !imce.getTbb('sendto')) {
return imce.addTbb('sendto', {
title: title || Drupal.t('Select'),
tooltip: desc || Drupal.t('Use the selected file.'),
handler: function () {
imce.runSendtoHandler();
},
icon: 'check'
});
}
};
/**
* Runs custom sendto handler on the first selected item.
*/
imce.runSendtoHandler = function (items) {
var handler = imce.sendtoHandler;
if (handler) {
var Item;
var imgType = imce.sendtoType === 'image';
items = items || imce.getSelection();
for (var i in items) {
if (imce.owns(items, i)) {
Item = items[i];
if (imgType ? Item.isImageSource() : Item.isFile) {
return handler(Item, window);
}
}
}
}
};
/**
* Default handler for activateFolder event.
*/
imce.defaultActivateFolder = function (Folder, oldFolder) {
// Enable/disable toolbar buttons by permission.
var i;
var j;
var Tbb;
var perm;
var disabled;
var buttons = imce.toolbarButtons;
for (i in buttons) {
if (!imce.owns(buttons, i)) {
continue;
}
Tbb = buttons[i];
if (perm = Tbb.permission) {
perm = perm.split('|');
disabled = true;
for (j in perm) {
if (Folder.getPermission(perm[j])) {
disabled = false;
break;
}
}
Tbb.setDisabled(disabled);
}
}
};
/**
* Updates the active sort state in the content header.
*/
imce.updateHeader = function () {
var newsort = imce.activeFolder.activeSort;
var oldsort = imce.activeSort || {};
var el = imce.contentHeaderEl;
// Check if the sort has changed.
if (newsort && (newsort.key !== oldsort.key || newsort.desc !== oldsort.desc)) {
// Deactivate existing column
if (oldsort.key) {
$('[data-sort="' + oldsort.key + '"]', el).removeClass('sorted ' + (oldsort.desc ? 'desc' : 'asc'));
}
// Activate new column
$('[data-sort="' + newsort.key + '"]', el).addClass('sorted ' + (newsort.desc ? 'desc' : 'asc'));
// Store the values
imce.activeSort = newsort;
}
};
/**
* Schedule preview updating.
*/
imce.updatePreview = function () {
clearTimeout(imce.previewTimer);
imce.previewTimer = setTimeout(imce.doUpdatePreview, 100);
};
/**
* Set preview of currently selected item.
*/
imce.doUpdatePreview = function () {
imce.previewItem(imce.countSelection() === 1 ? imce.getLastSelected() : null);
};
/**
* Sets/clears item preview.
*/
imce.previewItem = function (Item) {
var currentItem = imce.previewingItem;
if (imce.previewingItem = Item) {
$(imce.previewEl).html(Item.createPreviewEl());
imce.trigger('previewItem', Item);
}
else if (currentItem) {
imce.previewEl.innerHTML = '';
}
};
/**
* Schedule status update.
*/
imce.updateStatus = function () {
clearTimeout(imce.statusTimer);
imce.statusTimer = setTimeout(imce.doUpdateStatus, 100);
};
/**
* Updates active folder status.
*/
imce.doUpdateStatus = function () {
var Folder = imce.activeFolder;
if (Folder) {
$(imce.contentStatusEl).html(Folder.formatStatus());
}
};
/**
* Returns name filtering regexp.
*/
imce.getNameFilter = function () {
var filters = imce.getConf('name_filters', []);
// Dot files
if (!imce.getConf('allow_dot_files')) {
filters.push('^\\.|\\.$');
}
return filters.length ? new RegExp(filters.join('|')) : false;
};
/**
* Groups an array of items by parent folder and type.
*/
imce.groupItems = function (items) {
var i;
var Item;
var path;
var selected;
var key;
var names;
var group = {};
for (i in items) {
if (!imce.owns(items, i)) {
continue;
}
Item = items[i];
path = Item.parent.getPath();
selected = group[path] = imce.owns(group, path) ? group[path] : {};
key = Item.isFolder ? 'subfolders' : 'files';
names = selected[key] = selected[key] || {};
names[Item.name] = Item;
}
return group;
};
/**
* Checks parent folder permissions of the given items.
*/
imce.validatePermissions = function (items, filePerm, subfolderPerm) {
var path;
var Folder;
var selected;
var groups = imce.groupItems(items);
for (path in groups) {
if (!imce.owns(groups, path)) {
continue;
}
Folder = imce.getFolder(path);
selected = groups[path];
// Check file permission if the selection contains files
if (selected.files && (filePerm == null || !Folder.getPermission(filePerm))) {
return false;
}
// Check folder permission if the selection contains folders
if (selected.subfolders && (subfolderPerm == null || !Folder.getPermission(subfolderPerm))) {
return false;
}
}
return true;
};
/**
* Checks if items contain any predefined folder.
*/
imce.validatePredefinedPath = function (items) {
var i;
var Item;
var Folder;
for (i in items) {
if (!imce.owns(items, i)) {
continue;
}
Item = items[i];
if (Item.isFolder) {
if (Folder = Item.hasPredefinedPath()) {
imce.setMessage(Drupal.t('%path is a predefined path and can not be modified.', {'%path': Folder.getPath()}));
return false;
}
}
}
return true;
};
/**
* Validates the number of items.
*/
imce.validateCount = function (items) {
if (!items.length) {
imce.setMessage(Drupal.t('Please select a file.'));
return false;
}
return true;
};
/**
* Validates item extensions against an allowed list.
*/
imce.validateExtensions = function (items, exts) {
for (var i in items) {
if (imce.owns(items, i) && !imce.validateExtension(items[i].ext, exts)) {
return false;
}
}
return true;
};
/**
* Validates an extension against an allowed list.
*/
imce.validateExtension = function (ext, exts) {
if (!ext || $.inArray(ext.toLowerCase(), exts.toLowerCase().split(/[\s,]+/)) === -1) {
imce.setMessage(Drupal.t('Only files with the following extensions are allowed: %files-allowed.', {'%files-allowed': exts}));
return false;
}
return true;
};
/**
* Validates a file name.
*/
imce.validateFileName = function (name) {
// Basic validation
if (!name || name === '.' || name === '..' || !name.length || name.length > 240) {
return false;
}
// Test name filters
var regex = imce.getNameFilter();
if (regex && regex.test(name)) {
imce.setMessage(Drupal.t('%filename is not allowed.', {'%filename': name}));
return false;
}
// Test chars forbidden in various operating systems.
if (/^\s|\s$|[\/\\:\*\?\x22<>\|\x00-\x1F]/.test(name)) {
imce.setMessage(Drupal.t('%filename contains invalid characters. Use only alphanumeric characters for better portability.', {'%filename': name}));
return false;
}
return true;
};
/**
* Validates min/max image dimensions.
*/
imce.validateDimensions = function (items, width, height) {
// Check min dimensions
if (width < 1 || height < 1) {
return false;
}
// Check max dimensions.
var maxwidth = imce.getConf('maxwidth');
var maxheight = imce.getConf('maxheight');
if (maxwidth && width > maxwidth || maxheight && height > maxheight) {
imce.setMessage(Drupal.t('Image dimensions must be smaller than %dimensions pixels.', {'%dimensions': maxwidth + 'x' + maxwidth}));
return false;
}
return true;
};
/**
* Checks if all the selected items are images.
*/
imce.validateImageTypes = function (items) {
var Item = imce.getFirstItem(items, 'width', false);
if (Item) {
imce.setMessage(Drupal.t('%name is not an image.', {'%name': Item.name}));
return false;
}
return true;
};
/**
* Keydown event for the file manager.
*/
imce.eFmKeydown = function (event) {
return imce.eFireShortcut.call(this, event);
};
/**
* Refresh handler for the file manager.
*/
imce.eFmRefresh = function () {
imce.activeFolder.load();
};
/**
* Mousedown event for folder tree.
*/
imce.eTreeMousedown = function (event) {
// Manually focus as the browser default might have been prevented.
this.focus();
};
/**
* Touchstart event for folder tree.
*/
imce.eTreeTouchstart = function (event) {
this.focus();
};
/**
* Keydown event for folder tree.
*/
imce.eTreeKeydown = function (event) {
return imce.eFireShortcut.call(this, event, 'tree');
};
/**
* Tree shortcut: Enter.
*/
imce.eTreeEnter = function () {
imce.activeFolder.open();
};
/**
* Tree shortcut: UP.
*/
imce.eTreeUp = function (e) {
var Folder = imce.activeFolder;
var prvEl;
var prvFolder;
if (prvEl = Folder.branchEl.previousSibling) {
if (prvFolder = prvEl.Folder) {
while (prvFolder.expanded) {
if (prvEl = prvFolder.subtreeEl.lastChild) {
prvFolder = prvEl.Folder;
}
}
}
}
else {
prvFolder = Folder.parent;
}
if (prvFolder) {
prvFolder.activate();
imce.scrollToEl(prvFolder.branchEl, imce.treeEl);
}
};
/**
* Tree shortcut: DOWN.
*/
imce.eTreeDown = function (e) {
var Folder = imce.activeFolder;
var nextEl;
var nextFolder;
if (Folder.expanded && (nextEl = Folder.subtreeEl.firstChild)) {
nextFolder = nextEl.Folder;
}
else {
// noinspection Eslint
do {
if (nextEl = Folder.branchEl.nextSibling) {
nextFolder = nextEl.Folder;
break;
}
} while (Folder = Folder.parent);
}
if (nextFolder) {
nextFolder.activate();
imce.scrollToEl(nextFolder.branchEl, imce.treeEl);
}
};
/**
* Tree shortcut: LEFT/RIGHT.
*/
imce.eTreeLR = function (e) {
var Folder = imce.activeFolder;
if (e.keyCode === 39 ^ Folder.expanded) {
$(Folder.branchToggleEl).click();
}
};
/**
* Mousedown event for tree resizer.
*/
imce.eTreeResizerMousedown = function (event) {
return imce.eTreeResizerDown.call(this, imce.eFix(event));
};
/**
* Touch start event for tree resizer.
*/
imce.eTreeResizerTouchstart = function (event) {
return imce.eCommonTouchstart(event, imce.eTreeResizerDown, this);
};
/**
* Common Down event for tree resizer.
*/
imce.eTreeResizerDown = function (e, isTouch) {
this.startX = e.pageX;
this.startW = $(imce.treeEl).width();
this.maxW = this.startW + $(imce.contentEl).width();
imce.bindDragDrop(imce.eTreeResizerDrag, null, null, isTouch);
return false;
};
/**
* Drag event for tree resizer.
*/
imce.eTreeResizerDrag = function (e) {
var el = imce.treeResizerEl;
$(imce.treeEl).width(Math.min(el.maxW, Math.max(el.startW + e.pageX - el.startX, 0)));
e.preventDefault();
};
/**
* Mousedown event for content area.
*/
imce.eContentMousedown = function (event) {
// Manually focus as the browser default might have been prevented.
this.focus();
};
/**
* Touchstart event for content area.
*/
imce.eContentTouchstart = function (event) {
this.focus();
};
/**
* Keydown event for content area.
*/
imce.eContentKeydown = function (event) {
return imce.eFireShortcut.call(this, event, 'content');
};
/**
* Scroll event for content area.
*/
imce.eContentScroll = function (event) {
imce.updateContentPositions();
setTimeout(imce.updateContentPositions);
};
/**
* Click event for content header.
*/
imce.eContentHeaderClick = function (event) {
var key;
var e = imce.eFix(event);
var Folder = imce.activeFolder;
var sort = Folder.activeSort || {};
if (key = e.target.getAttribute('data-sort')) {
Folder.sortItems(key, key === sort.key ? !sort.desc : sort.desc);
}
};
/**
* Update content header position on content scroll.
*/
imce.updateContentPositions = function () {
var top = imce.contentEl.scrollTop;
imce.contentHeaderEl.style.top = top + 'px';
imce.contentStatusEl.style.bottom = -top + 'px';
};
/**
* Content shortcut: ENTER.
*/
imce.eContentEnter = function (e) {
var Item = imce.getLastSelected();
if (Item) {
Item.dblClick();
}
};
/**
* Content shortcut: Ctrl+A.
*/
imce.eContentCtrlA = function (e) {
imce.selectAll();
};
/**
* Content shortcut: LEFT/RIGHT/UP/DOWN
*/
imce.eContentArrow = function (e) {
var Item;
var i = 0;
var Folder = imce.activeFolder;
var key = e.keyCode;
if (Item = imce.getLastSelected()) {
i = Folder.indexOf(Item) + (key % 2 ? key - 38 : imce.countElPerRow(Item.el) * (key - 39));
}
if (Item = Folder.getItemAt(i)) {
Item.click(e);
Item.scrollIntoView();
}
};
/**
* Mousedown event for body resizer.
*/
imce.eBodyResizerMousedown = function (event) {
return imce.eBodyResizerDown.call(this, imce.eFix(event));
};
/**
* Touch start event for body resizer.
*/
imce.eBodyResizerTouchstart = function (event) {
return imce.eCommonTouchstart(event, imce.eBodyResizerDown, this);
};
/**
* Common Down event for body resizer.
*/
imce.eBodyResizerDown = function (e, isTouch) {
this.startY = e.pageY;
this.startH = $(imce.bodyEl).height();
this.maxH = this.startH + $(imce.previewEl).height();
imce.bindDragDrop(imce.eBodyResizerDrag, null, null, isTouch);
return false;
};
/**
* Drag event for body resizer.
*/
imce.eBodyResizerDrag = function (e) {
var el = imce.bodyResizerEl;
var bodyH = Math.min(Math.max(el.startH + e.pageY - el.startY, 0), el.maxH);
$(imce.bodyEl).height(bodyH);
$(imce.previewEl).height(el.maxH - bodyH);
e.preventDefault();
};
/**
* Beforeunload event for window.
*/
imce.eWinBeforeunload = function (e) {
// Store active sort.
var data = {};
if (data.activeSort = imce.activeSort) {
imce.trigger('storeLocalData', data);
try {
localStorage.setItem('imce.local', JSON.stringify(data));
}
catch (err) {
imce.delayError(err);
}
}
};
/**
* Resize event for window.
*/
imce.eWinResize = function (e) {
var pdiff;
var diff = imce.getWindowSize().height - imce.fmEl.offsetHeight;
// Distribute the excess space to the body and preview elements.
if (diff) {
var $bodyEl = $(imce.bodyEl);
var $prvEl = $(imce.previewEl);
if ($prvEl[0].offsetHeight) {
pdiff = parseInt(diff / 2);
diff -= pdiff;
$prvEl.height($prvEl.height() + pdiff);
}
$bodyEl.height($bodyEl.height() + diff);
}
};
/**
* Binds an handler by type.
*/
imce.bind = function (type, handler) {
var events = imce.events;
var handlers = events[type];
if (!handlers) {
handlers = events[type] = {};
}
handlers['' + handler] = handler;
};
/**
* Unbinds an handler by type.
*/
imce.unbind = function (type, handler) {
var events = imce.events;
var handlers = events[type];
if (handlers) {
if (1 in arguments) {
delete handlers['' + handler];
}
else {
delete events[type];
}
}
};
/**
* Triggers handlers by type.
*/
imce.trigger = function (type) {
var i;
var handler;
var handlers = imce.events[type];
var ret = [];
if (handlers) {
for (i in handlers) {
if (handler = handlers[i]) {
if (handler.apply) {
ret.push(handler.apply(imce, Array.prototype.slice.call(arguments, 1)));
}
}
}
}
return ret;
};
/**
* Adds a shortcut handler to an area.
*/
imce.addShortcut = function (shortcut, handler, area) {
var shortcuts;
if (shortcuts = imce.getAreaShortcuts(area)) {
shortcuts[shortcut.toUpperCase()] = handler;
}
};
/**
* Returns a shortcut handler.
*/
imce.getShortcut = function (shortcut, area) {
var shortcuts;
if (shortcuts = imce.getAreaShortcuts(area)) {
return shortcuts[shortcut.toUpperCase()];
}
};
/**
* Removes a shortcut handler.
*/
imce.removeShortcut = function (shortcut, area) {
var shortcuts;
if (shortcuts = imce.getAreaShortcuts(area)) {
delete shortcuts[shortcut.toUpperCase()];
}
};
/**
* Executes a shortcut handler.
* Returns true if shortcut exists and is executed successfully.
*/
imce.fireShortcut = function (shortcut, area) {
var handler = imce.getShortcut(shortcut, area);
if (handler) {
// DOM element
if (handler.click) {
handler.click();
return true;
}
// Callback
if (handler.apply) {
// Shortcuts returning false are considered disabled.
return handler.apply(this, Array.prototype.slice.call(arguments, 2)) !== false;
}
}
};
/**
* Returns a shortcut handler.
*/
imce.getAreaShortcuts = function (area) {
if (!area) {
area = 'fm';
}
return imce.shortcuts[area];
};
/**
* Builds a shortcut string from an event.
*/
imce.eBuildShortcut = function (e) {
var symbol;
var key = e.keyCode;
var shortcut = '';
if (key && (symbol = imce.getKeySymbols(key))) {
if (e.ctrlKey) {
shortcut += 'CTRL+';
}
if (e.altKey) {
shortcut += 'ALT+';
}
if (e.shiftKey) {
shortcut += 'SHIFT+';
}
shortcut += symbol;
}
return shortcut;
};
/**
* Event helper for imce shortcut firing.
*/
imce.eFireShortcut = function (event, area) {
var e = event || window.event;
var shortcut = imce.eBuildShortcut(e);
// Prevent default if shortcut is executed.
if (shortcut) {
e = $.event.fix(e);
if (imce.fireShortcut.call(this, shortcut, area, e)) {
e.stopPropagation();
return false;
}
}
};
/**
* Returns key symbols allowed in shortcuts.
*/
imce.getKeySymbols = function (key) {
var i;
var symbols = imce.keySymbols;
if (!symbols) {
// Custom keys
symbols = imce.keySymbols = {
8: 'BACKSPACE',
9: 'TAB',
13: 'ENTER',
27: 'ESC',
32: 'SPACE',
37: 'LEFT',
38: 'UP',
39: 'RIGHT',
40: 'DOWN',
46: 'DEL'
};
// Add numbers
for (i = 0; i < 10; i++) {
symbols[48 + i] = '' + i;
}
// Add letters
for (i = 65; i < 91; i++) {
symbols[i] = String.fromCharCode(i);
}
// Add function keys
for (i = 1; i < 13; i++) {
symbols[111 + i] = 'F' + i;
}
}
return (0 in arguments) ? symbols[key] : symbols;
};
/**
* Creates an ajax request for a specific operation.
*/
imce.ajax = function (jsop, opt) {
return $.ajax(imce.ajaxPrepare(jsop, opt));
};
/**
* Prepares ajax options.
*/
imce.ajaxPrepare = function (jsop, opt) {
// Prepare data
var path;
var Folder = opt && opt.activeFolder != null ? opt.activeFolder : imce.activeFolder;
var data = {jsop: jsop, token: imce.getConf('token')};
if (Folder) {
if (path = Folder.getPath()) {
data.active_path = path;
}
}
// Extend defaults
return $.extend(true, imce.ajaxDefaults(), {data: data, activeFolder: Folder}, opt);
};
/**
* Returns ajax default options.
*/
imce.ajaxDefaults = function () {
return {
url: imce.getConf('ajax_url'),
type: 'POST',
dataType: 'json',
beforeSend: imce.ajaxBeforeSend,
success: imce.ajaxSuccess,
error: imce.ajaxError,
complete: imce.ajaxComplete
};
};
/**
* Creates an ajax request for a specific operation on the given items.
*/
imce.ajaxItems = function (jsop, items, opt) {
return imce.ajax(jsop, $.extend(true, imce.ajaxItemsOpt(items), opt));
};
/**
* Creates an ajax options object including the items as the selection data.
*/
imce.ajaxItemsOpt = function (items) {
return {data: {selection: imce.getItemPaths(items)}};
};
/**
* Default before send handler.
*/
imce.ajaxBeforeSend = function (xhr, opt) {
var handler;
var Folder;
if (handler = opt.customBeforeSend) {
if (handler.apply(this, arguments) === false) {
opt.activeFolder = null;
return false;
}
}
if (Folder = opt.activeFolder) {
Folder.setBusy(true);
}
};
/**
* Default ajax success handler.
*/
imce.ajaxSuccess = function (response, status) {
var handler;
var opt = this;
// Make the response available in complete handlers.
opt.response = response;
imce.ajaxProcessResponse(response);
if (handler = opt.customSuccess) {
handler.apply(opt, arguments);
}
};
/**
* Default ajax complete handler.
*/
imce.ajaxComplete = function (xhr, status) {
var Folder;
var handler;
var opt = this;
if (Folder = opt.activeFolder) {
Folder.setBusy(false);
}
if (handler = opt.customComplete) {
handler.apply(opt, arguments);
}
opt.response = opt.activeFolder = null;
};
/**
* Default ajax error handler.
*/
imce.ajaxError = function (xhr, status, e) {
if (status !== 'abort') {
imce.setMessage('<pre class="imce-ajax-error">' + Drupal.checkPlain(imce.ajaxErrorMessage(xhr, this.url)) + '</pre>');
}
};
/**
* Processes the ajax response.
*/
imce.ajaxProcessResponse = function (response) {
if (response) {
imce.ajaxProcessRemoved(response);
imce.ajaxProcessAdded(response);
imce.ajaxProcessMessages(response);
}
};
/**
* Processes the added items in the response.
*/
imce.ajaxProcessAdded = function (response) {
var path;
var Folder;
var added;
if (added = response.added) {
for (path in added) {
if (Folder = imce.addFolder(path)) {
Folder.addContent(added[path], true);
imce.contentEl.focus();
}
}
}
};
/**
* Processes the removed items in the response.
*/
imce.ajaxProcessRemoved = function (response) {
var i;
var Item;
var paths = response.removed;
if (paths) {
for (i in paths) {
if (Item = imce.getItem(paths[i])) {
Item.remove();
}
}
}
};
/**
* Processes the messages in the response.
*/
imce.ajaxProcessMessages = function (response) {
var i;
var type;
var msgs = response.messages;
if (msgs) {
for (type in msgs) {
if (imce.owns(msgs, type)) {
for (i in msgs[type]) {
if (msgs[type].hasOwnProperty(i)) {
imce.setMessage(msgs[type][i], type);
}
}
}
}
}
};
/**
* Generates an ajax error message.
*/
imce.ajaxErrorMessage = function (xhr, url) {
var msg = Drupal.t('An AJAX HTTP error occurred.');
msg += '\n' + Drupal.t('Path: !uri', {'!uri': url});
msg += '\n' + Drupal.t('HTTP Result Code: !status', {'!status': xhr.status || 0});
msg += '\n' + Drupal.t('StatusText: !statusText', {'!statusText': xhr.statusText || 'N/A'});
msg += '\n' + Drupal.t('ResponseText: !responseText', {'!responseText': xhr.responseText || 'N/A'});
return msg;
};
/**
* Returns an array of item paths.
*/
imce.getItemPaths = function (items) {
return $.map(items, imce.getItemPath);
};
/**
* Returns the path of an item.
*/
imce.getItemPath = function (Item) {
return Item.getPath();
};
/**
* Set a status message.
*/
imce.setMessage = function (msg, type) {
if (!type) {
type = 'error';
}
var mq = imce.messageQueue;
var len = mq.length;
var msgId = msg + ':' + type;
// Skip if it's identical to the last message
if (len && mq[len - 1].msgId === msgId) {
return false;
}
// Add the message
mq[len] = imce.createMessageEl(msg, type);
mq[len].msgId = msgId;
// Schedule the processing at a later time to queue new messages.
if (!imce.pmqScheduled) {
imce.pmqScheduled = setTimeout(imce.processMessageQueue, 100);
}
return false;
};
/**
* Process message queue.
*/
imce.processMessageQueue = function () {
var mq = imce.messageQueue;
if (mq.length) {
// Display all messages
$(imce.createMessagePopupEl()).html(mq).fadeIn(200);
// Empty array.
mq.length = 0;
// Mousedown close
$(document).bind('mousedown', imce.eMPopDocMousedown);
// Auto close
imce.mPopCloseTimer = setTimeout(imce.mPopClose, 2500);
}
};
/**
* Closes currently open message popup.
*/
imce.mPopClose = function () {
// Time up but still hovering. Do not close. A new timer will be set on mouseout.
if (imce.mPopHovering) {
imce.mPopCloseTimerUp = 1;
return imce.mPopCloseTimerUp;
}
// Time up or mousedown
clearTimeout(imce.mPopCloseTimer);
imce.mPopCloseTimerUp = 0;
$(document).unbind('mousedown', imce.eMPopDocMousedown);
$(imce.messagePopupEl).fadeOut(400, imce.processMessageQueueNext);
};
/**
* Continue processing the remaining messages.
*/
imce.processMessageQueueNext = function () {
imce.pmqScheduled = 0;
if (imce.messageQueue.length) {
imce.pmqScheduled = setTimeout(imce.processMessageQueue, 250);
}
};
/**
* Mouseover event for message popup.
*/
imce.eMPopMouseenter = function (e) {
imce.mPopHovering = 1;
// Clear the shorter timer set on mouseleave
if (imce.mPopCloseTimerUp) {
clearTimeout(imce.mPopCloseTimer);
}
};
/**
* Mouseout event for message popup.
*/
imce.eMPopMouseleave = function (e) {
imce.mPopHovering = 0;
// Set a shorter close timer if the long time is up
if (imce.mPopCloseTimerUp) {
imce.mPopCloseTimer = setTimeout(imce.mPopClose, 2000);
}
};
/**
* Mousedown event for document in order to close message popup.
*/
imce.eMPopDocMousedown = function (e) {
// Close the popup if the mousedown is outside of it.
if (!imce.mPopHovering) {
imce.mPopClose();
}
};
/**
* Creates a message element.
*/
imce.createMessageEl = function (msg, type) {
var el = imce.createEl('<div class="imce-message imce-ficon"><div class="imce-message-content"></div></div>');
el.className += ' ' + type;
el.firstChild.innerHTML = msg;
return el;
};
/**
* Creates the message popup element.
*/
imce.createMessagePopupEl = function () {
var el = imce.messagePopupEl;
if (!el) {
el = imce.messagePopupEl = imce.createLayer('imce-message-popup', imce.fmEl);
$(el).hover(imce.eMPopMouseenter, imce.eMPopMouseleave);
}
return el;
};
/**
* Checks a permission in a folder conf.
*/
imce.permissionInFolderConf = function (permission, folderConf) {
var permissions = folderConf && folderConf.permissions;
return !!(permissions && ((permission in permissions) ? permissions[permission] : permissions.all));
};
/**
* Checks if a permission exists in any of the predefined folders.
*/
imce.hasPermission = function (permission, conf) {
var i;
var folders = (conf || imce.conf).folders;
if (folders) {
for (i in folders) {
if (imce.permissionInFolderConf(permission, folders[i])) {
return true;
}
}
}
return false;
};
/**
* Sorting helpers.
*/
imce.sortText = function (a, b) {
return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
};
imce.sortNum = function (a, b) {
return (a || 0) - (b || 0);
};
imce.sortNumericProperty = function (a, b, prop) {
// Do not change sort within folders
var result = (a.isFolder ? -1 : 1);
if (a.isFolder === b.isFolder) {
result = imce.sortNum(a[prop] || 0, b[prop] || 0);
}
return result;
};
imce.sortBranchName = function (a, b) {
return imce.sortText(a.name, b.name);
};
/**
* Property sorters.
*/
imce.sorters.name = function (a, b) {
var result = (a.isFolder ? -1 : 1);
if (a.isFolder === b.isFolder) {
result = imce.sortText(a.name, b.name);
}
return result;
};
imce.sorters.date = function (a, b) {
return imce.sortNumericProperty(a, b, 'date');
};
imce.sorters.size = function (a, b) {
return imce.sortNumericProperty(a, b, 'size');
};
imce.sorters.width = function (a, b) {
return imce.sortNumericProperty(a, b, 'width');
};
imce.sorters.height = function (a, b) {
return imce.sortNumericProperty(a, b, 'height');
};
imce.sorters.ext = function (a, b) {
var result = (a.isFolder ? -1 : 1);
if (a.isFolder === b.isFolder) {
result = (a.isFolder ? 0 : imce.sortText(a.ext || '', b.ext || ''));
}
return result;
};
/**
* Splits a path into dirpath and filename.
*/
imce.splitPath = function (path) {
if (typeof path === 'string' && path !== '') {
var parts = path.split('/');
var filename = parts.pop();
var dirpath = parts.join('/');
if (filename !== '') {
return [dirpath === '' ? '.' : dirpath, filename];
}
}
};
/**
* Creates a file path by joining a folder path and a filename.
*/
imce.joinPaths = function (dirpath, filename) {
if (dirpath === '.') {
return filename;
}
if (filename === '.') {
return dirpath;
}
if (dirpath.substr(-1) !== '/') {
dirpath += '/';
}
return dirpath + filename;
};
/**
* Returns query parameters from the current URL.
*/
imce.getQuery = function (name) {
var i;
var part;
var parts;
var str;
var query = imce.query;
if (!query) {
query = imce.query = {};
if (str = location.search) {
parts = str.substr(1).split('&');
for (i in parts) {
if (imce.owns(parts, i)) {
part = parts[i].split('=');
query[imce.decode(part[0])] = part[1] ? imce.decode(part[1]) : '';
}
}
}
}
return name ? query[name] : query;
};
/**
* Wrapper around decodeURIComponent.
* Avoids malformed uri exception.
*/
imce.decode = function (str) {
try {
str = decodeURIComponent(str);
}
catch (err) {
imce.delayError(err);
}
return str;
};
/**
* Throws an error after a minimum delay.
*/
imce.delayError = function (err) {
setTimeout(function () {
throw err;
});
};
/**
* Formats item date.
*/
imce.formatDate = function (timestamp, dayOnly) {
var D;
var p0;
var ret = '';
if (timestamp) {
D = new Date(timestamp * 1000);
p0 = imce.prependZero;
ret = D.getFullYear() + '-' + p0(D.getMonth() + 1) + '-' + p0(D.getDate());
if (!dayOnly) {
ret += ' ' + p0(D.getHours()) + ':' + p0(D.getMinutes());
}
}
return ret;
};
/**
* Formats item size.
*/
imce.formatSize = function (size) {
if (size == null) {
return '';
}
if (!size || size < 100) {
return Drupal.formatPlural(size, '1 byte', '@count bytes', {'@count': size || 0});
}
if (size < 1048576) {
return Drupal.t('@size KB', {'@size': imce.round(size / 1024, 1)});
}
return Drupal.t('@size MB', {'@size': imce.round(size / 1048576, 1)});
};
/**
* Formats content items status.
*/
imce.formatItemsStatus = function (count, size) {
return Drupal.t('!items (!size)', {
'!items': Drupal.formatPlural(count, '1 item', '@count items'),
'!size': imce.formatSize(size)
});
};
/**
* Prepends 0 to numbers smaller than 10.
*/
imce.prependZero = function (num) {
return num < 10 ? '0' + num : num;
};
/**
* Rounds a number to the given precision
*/
imce.round = function (num, precision) {
var n = Math.pow(10, precision);
return Math.round(num * n) / n;
};
/**
* Returns the extension of a file name.
*/
imce.getExt = function (name) {
var pos = name.lastIndexOf('.');
return pos === -1 ? '' : name.substr(pos + 1);
};
/**
* Checks if an object owns a property.
*/
imce.owns = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
/**
* Returns the first item that has a property.
*/
imce.getFirstItem = function (items, prop, state) {
var i;
var item;
if (typeof state === "undefined") {
state = true;
}
for (i in items) {
if (imce.owns(items, i)) {
item = items[i];
if (!prop || (item[prop] ? state : !state)) {
return item;
}
}
}
};
/**
* Resolves a string to a handler under the given scope.
*/
imce.resolveHandler = function (str, scope) {
if (!str) {
return;
}
var i;
var obj = scope || window;
var parts = str.split('.');
var len = parts.length;
for (i = 0; i < len && (obj = obj[parts[i]]); i++) {
// empty
}
return i === len && obj && obj.call && obj.apply ? obj : false;
};
/**
* Creates a DOM element from html string.
*/
imce.createEl = function (html) {
var el;
var div = imce._div;
if (!div) {
div = imce._div = document.createElement('div');
}
div.innerHTML = html;
el = div.firstChild;
div.removeChild(el);
return el;
};
/**
* Creates a layer element.
*/
imce.createLayer = function (cname, parent) {
var layer = imce.createEl('<div class="imce-layer"></div>');
if (cname) {
layer.className += ' ' + cname;
}
if (parent !== false) {
(parent || document.body).appendChild(layer);
}
return layer;
};
/**
* Removes element without removing events.
*/
imce.removeEl = function (el) {
var parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
};
/**
* Returns window inner size.
*/
imce.getWindowSize = function () {
return {
width: window.innerWidth || document.documentElement.clientWidth,
height: window.innerHeight || document.documentElement.clientHeight
};
};
/**
* Returns scroll values of the window.
*/
imce.getWindowScroll = function () {
if (typeof window.pageXOffset === "undefined") {
var el = document.documentElement;
return {left: el.scrollLeft, top: el.scrollTop};
}
return {left: window.pageXOffset, top: window.pageYOffset};
};
/**
* Fixes and converts an event into jQuery event.
*/
imce.eFix = function (event) {
return $.event.fix(event || window.event);
};
/**
* Scroll an element into view inside the scrollable wrapper.
*/
imce.scrollToEl = function (el, wrpEl, diffTop, diffBottom) {
if (el.offsetWidth && wrpEl.scrollHeight > wrpEl.clientHeight) {
var elTop = $(el).offset().top;
var elBottom = elTop + el.offsetHeight;
var wrpTop = $(wrpEl).offset().top;
var wrpBottom = wrpTop + wrpEl.offsetHeight;
wrpTop += diffTop || 0;
wrpBottom -= diffBottom || 0;
// Check top positions
if (elTop < wrpTop) {
wrpEl.scrollTop -= wrpTop - elTop + 10;
}
else if (wrpBottom < elBottom) {
// Consider el height might be bigger than the wrapper height.
// Get the minimum among top space and required scroll.
wrpEl.scrollTop += Math.min(elBottom - wrpBottom + 10, elTop - wrpTop - 10);
}
}
};
/**
* Returns number of the elements that can fit in a row inside the parent.
*/
imce.countElPerRow = function (el) {
return Math.max(1, parseInt(el.parentNode.clientWidth / $(el).outerWidth(true)));
};
/**
* Makes the element stay inside window boundaries.
*/
imce.fixPosition = function (el) {
var pos = $(el).offset();
var winSize = imce.getWindowSize();
var winScroll = imce.getWindowScroll();
var scrollbar = 18;
var extraX = pos.left - winScroll.left + el.offsetWidth - winSize.width + scrollbar;
var extraY = pos.top - winScroll.top + el.offsetHeight - winSize.height + scrollbar;
// Shift to left
if (extraX > 0) {
el.style.left = Math.max(0, pos.left - extraX) + 'px';
}
// Shift to top
if (extraY > 0) {
el.style.top = Math.max(0, pos.top - extraY) + 'px';
}
};
/**
* Bind drag drop callbacks to the document
*/
imce.bindDragDrop = function (drag, drop, data, isTouch) {
var edata = {drag: drag, drop: drop, data: data, isTouch: isTouch};
$(document).bind(isTouch ? 'touchmove' : 'mousemove', edata, imce.eDocDrag).bind(isTouch ? 'touchend' : 'mouseup', edata, imce.eDocDrop);
};
/**
* Default drag event for document which is set by imce.bindDragDrop
*/
imce.eDocDrag = function (e) {
var edata = e.data;
// Call custom drag event if set.
if (edata.drag) {
// Fix touch event
if (edata.isTouch) {
e = imce.eTouchFix(e, e.originalEvent.changedTouches[0]);
}
return edata.drag.call(this, e);
}
};
/**
* Default drop event for document which is set by imce.bindDragDrop
*/
imce.eDocDrop = function (e) {
var edata = e.data;
$(document).unbind(edata.isTouch ? 'touchmove' : 'mousemove', imce.eDocDrag).unbind(edata.isTouch ? 'touchend' : 'mouseup', imce.eDocDrop);
// Call custom drop event if set.
if (edata.drop) {
// Fix touch event
if (edata.isTouch) {
e = imce.eTouchFix(e, e.originalEvent.changedTouches[0]);
}
return edata.drop.call(this, e);
}
};
/**
* Fix touch events
*/
imce.eTouchFix = function (e, touch) {
// Make sure e is a jquery event object that is writable.
e = $.event.fix(e);
if (touch && typeof touch.pageX !== "undefined") {
e.pageX = touch.pageX;
e.pageY = touch.pageY;
e.clientX = touch.clientX;
e.clientY = touch.clientY;
}
return e;
};
/**
* Common touchstart event.
*/
imce.eCommonTouchstart = function (event, callback, context) {
var touch;
var touches = event.changedTouches;
// Skip event for multi-touch
if (touches && (touch = touches[0]) && !touches[1]) {
if (callback && callback.call) {
return callback.call(context || this, imce.eTouchFix(event, touch), true);
}
// Prevent default.
return false;
}
};
})(jQuery, Drupal);