(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('
'); el.onkeydown = imce.eFmKeydown; el.tabIndex = 0; // Toolbar el.appendChild(imce.toolbarEl = createEl('')); // Body el.appendChild(imce.bodyEl = createEl('
')); // Tree el = imce.treeEl = createEl('
'); 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('
'); el.onmousedown = imce.eTreeResizerMousedown; el.ontouchstart = imce.eTreeResizerTouchstart; imce.bodyEl.appendChild(el); // Content el = imce.contentEl = createEl('
'); 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('
' + Drupal.t('Date') + '
' + Drupal.t('Height') + '
' + Drupal.t('Width') + '
' + Drupal.t('Size') + '
' + Drupal.t('Name') + '
'); el.onclick = imce.eContentHeaderClick; imce.contentEl.appendChild(el); // Content status el = imce.contentStatusEl = imce.createEl('
'); imce.contentEl.appendChild(el); // Body resizer el = imce.bodyResizerEl = createEl('
'); el.onmousedown = imce.eBodyResizerMousedown; el.ontouchstart = imce.eBodyResizerTouchstart; imce.fmEl.appendChild(el); // Preview imce.fmEl.appendChild(imce.previewEl = createEl('
')); 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('
' + Drupal.checkPlain(imce.ajaxErrorMessage(xhr, this.url)) + '
'); } }; /** * 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('
'); 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('
'); 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);