diff --git a/modules/cms/classes/MediaLibrary.php b/modules/cms/classes/MediaLibrary.php index 699137e38..b3f80a74c 100644 --- a/modules/cms/classes/MediaLibrary.php +++ b/modules/cms/classes/MediaLibrary.php @@ -208,6 +208,35 @@ class MediaLibrary return false; } + /** + * Returns a list of all directories in the Library, optionally excluding some of them. + * @param array $exclude A list of folders to exclude from the result list/ + * The folder paths should be specified relative to the Library root. + * @return array + */ + public function listAllDirectories($exclude = []) + { + $fullPath = $this->getMediaPath('/'); + + $folders = $this->getStorageDisk()->allDirectories($fullPath); + $folders = array_unique($folders, SORT_LOCALE_STRING); + + $result = []; + + foreach ($folders as $folder) { + $folder = $this->getMediaRelativePath($folder); + if (!strlen($folder)) + $folder = '/'; + + if (Str::startsWith($folder, $exclude)) + continue; + + $result[] = $folder; + } + + return $result; + } + /** * Returns a file contents. * @param string $path Specifies the file path relative the the Library root. diff --git a/modules/cms/lang/en/lang.php b/modules/cms/lang/en/lang.php index c61131926..4fbc2d668 100644 --- a/modules/cms/lang/en/lang.php +++ b/modules/cms/lang/en/lang.php @@ -260,12 +260,17 @@ return [ 'search' => 'Search', 'folder' => 'Folder', 'no_files_found' => 'No files found by your request.', - 'delete_empty' => 'Please select files to delete.', + 'delete_empty' => 'Please select items to delete.', 'delete_confirm' => 'Do you really want to delete the selected item(s)?', - 'error_renaming_file' => 'Error renaming file.', + 'error_renaming_file' => 'Error renaming the item.', 'new_folder_title' => 'New folder', 'folder_name' => 'Folder name', 'error_creating_folder' => 'Error creating folder', - 'folder_or_file_exist' => 'A folder or file with the specified name already exists.' + 'folder_or_file_exist' => 'A folder or file with the specified name already exists.', + 'move_empty' => 'Please select items to move.', + 'move_popup_title' => 'Move files or folders', + 'move_destination' => 'Destination folder', + 'please_select_move_dest' => 'Please select a destination folder.', + 'move_dest_src_match' => 'Please select another destination folder.' ] ]; diff --git a/modules/cms/widgets/MediaManager.php b/modules/cms/widgets/MediaManager.php index fa96a416e..913ba6d87 100644 --- a/modules/cms/widgets/MediaManager.php +++ b/modules/cms/widgets/MediaManager.php @@ -286,6 +286,69 @@ class MediaManager extends WidgetBase ]; } + public function onLoadMovePopup() + { + $exclude = Input::get('exclude', []); + if (!is_array($exclude)) + throw new SystemException('Invalid input data'); + + $folders = MediaLibrary::instance()->listAllDirectories($exclude); + + $folderList = []; + foreach ($folders as $folder) { + $path = $folder; + + if ($folder == '/') + $name = Lang::get('cms::lang.media.library'); + else { + $segments = explode('/', $folder); + $name = str_repeat(' ', (count($segments)-1)*4).basename($folder); + } + + $folderList[$path] = $name; + } + + $this->vars['folders'] = $folderList; + $this->vars['originalPath'] = Input::get('path'); + + return $this->makePartial('move-form'); + } + + public function onMoveItems() + { + $dest = trim(Input::get('dest')); + if (!strlen($dest)) + throw new ApplicationException(Lang::get('cms::lang.media.please_select_move_dest')); + + $dest = MediaLibrary::validatePath($dest); + if ($dest == Input::get('originalPath')) + throw new ApplicationException(Lang::get('cms::lang.media.move_dest_src_match')); + + $files = Input::get('files', []); + if (!is_array($files)) + throw new SystemException('Invalid input data'); + + $folders = Input::get('folders', []); + if (!is_array($folders)) + throw new SystemException('Invalid input data'); + + $library = MediaLibrary::instance(); + + foreach ($files as $path) + $library->moveFile($path, $dest.'/'.basename($path)); + + foreach ($folders as $path) + $library->moveFolder($path, $dest.'/'.basename($path)); + + $library->resetCache(); + + $this->prepareVars(); + + return [ + '#'.$this->getId('item-list') => $this->makePartial('item-list') + ]; + } + // // Methods for th internal use // diff --git a/modules/cms/widgets/mediamanager/assets/css/mediamanager.css b/modules/cms/widgets/mediamanager/assets/css/mediamanager.css index 98ca84d06..1c3341ca0 100644 --- a/modules/cms/widgets/mediamanager/assets/css/mediamanager.css +++ b/modules/cms/widgets/mediamanager/assets/css/mediamanager.css @@ -331,6 +331,9 @@ div[data-control="media-manager"] table.table tr:hover div.item-title { div[data-control="media-manager"] table.table tr:hover div.item-title a { display: block; } +div[data-control="media-manager"] table.table tr[data-item-type=folder] i.icon-folder { + color: #4da7e8; +} div[data-control="media-manager"] div[data-control="selection-marker"] { position: absolute; z-index: 50; diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager.js index db42595fa..0af71e03b 100644 --- a/modules/cms/widgets/mediamanager/assets/js/mediamanager.js +++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager.js @@ -27,6 +27,9 @@ this.folderPopupShownHandler = this.onFolderPopupShown.bind(this) this.newFolderSubmitHandler = this.onNewFolderSubmit.bind(this) this.folderPopupHiddenHandler = this.onFolderPopupHidden.bind(this) + this.movePopupShownHandler = this.onMovePopupShown.bind(this) + this.moveItemsSubmitHandler = this.onMoveItemsSubmit.bind(this) + this.movePopupHiddenHandler = this.onMovePopupHidden.bind(this) // Instance-bound methods this.updateSidebarPreviewBound = this.updateSidebarPreview.bind(this) @@ -45,6 +48,7 @@ this.deleteConfirmationBound = this.deleteConfirmation.bind(this) this.refreshBound = this.refresh.bind(this) this.folderCreatedBound = this.folderCreated.bind(this) + this.itemsMovedBound = this.itemsMoved.bind(this) // State properties this.selectTimer = null @@ -90,6 +94,7 @@ this.deleteConfirmationBound = null this.refreshBound = null this.folderCreatedBound = null + this.itemsMovedBound = null this.sidebarPreviewElement = null this.itemListElement = null @@ -121,6 +126,8 @@ this.$el.on('mediarefresh', this.refreshBound) this.$el.on('shown.oc.popup', '[data-command="create-folder"]', this.folderPopupShownHandler) this.$el.on('hidden.oc.popup', '[data-command="create-folder"]', this.folderPopupHiddenHandler) + this.$el.on('shown.oc.popup', '[data-command="move"]', this.movePopupShownHandler) + this.$el.on('hidden.oc.popup', '[data-command="move"]', this.movePopupHiddenHandler) if (this.itemListElement) this.itemListElement.addEventListener('mousedown', this.listMouseDownHandler) @@ -135,6 +142,8 @@ this.$el.off('keyup', '[data-control="search"]', this.searchChangedHandler) this.$el.off('shown.oc.popup', '[data-command="create-folder"]', this.folderPopupShownHandler) this.$el.off('hidden.oc.popup', '[data-command="create-folder"]', this.folderPopupHiddenHandler) + this.$el.off('shown.oc.popup', '[data-command="move"]', this.movePopupShownHandler) + this.$el.off('hidden.oc.popup', '[data-command="move"]', this.movePopupHiddenHandler) if (this.itemListElement) { this.itemListElement.removeEventListener('mousedown', this.listMouseDownHandler) @@ -153,6 +162,9 @@ this.folderPopupShownHandler = null this.folderPopupHiddenHandler = null this.newFolderSubmitHandler = null + this.movePopupShownHandler = null + this.moveItemsSubmitHandler = null + this.movePopupHiddenHandler = null } MediaManager.prototype.changeView = function(view) { @@ -710,7 +722,7 @@ // File and folder operations // - MediaManager.prototype.deleteFiles = function() { + MediaManager.prototype.deleteItems = function() { var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') if (!items.length) { @@ -793,6 +805,81 @@ this.afterNavigateBound } + MediaManager.prototype.moveItems = function(ev) { + var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected') + + if (!items.length) { + swal({ + title: this.options.moveEmpty, + confirmButtonClass: 'btn-default' + }) + + return + } + + var data = { + exclude: [], + path: this.$el.find('[data-type="current-folder"]').val() + } + + for (var i = 0, len = items.length; i < len; i++) { + var item = items[i], + path = item.getAttribute('data-path') + + if (item.getAttribute('data-item-type') == 'folder') + data.exclude.push(path) + } + + $(ev.target).popup({ + handler: this.options.alias+'::onLoadMovePopup', + extraData: data + }) + } + + MediaManager.prototype.onMovePopupShown = function(ev, button, popup) { + $(popup).on('submit.media', 'form', this.moveItemsSubmitHandler) + } + + MediaManager.prototype.onMoveItemsSubmit = function(ev) { + var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'), + data = { + dest: $(ev.target).find('select[name=dest]').val(), + originalPath: $(ev.target).find('input[name=originalPath]').val(), + files: [], + folders: [] + } + + for (var i = 0, len = items.length; i < len; i++) { + var item = items[i], + path = item.getAttribute('data-path') + + if (item.getAttribute('data-item-type') == 'folder') + data.folders.push(path) + else + data.files.push(path) + } + + $.oc.stripeLoadIndicator.show() + this.$form.request(this.options.alias+'::onMoveItems', { + data: data + }).always(function() { + $.oc.stripeLoadIndicator.hide() + }).done(this.itemsMovedBound) + + ev.preventDefault() + return false + } + + MediaManager.prototype.onMovePopupHidden = function(ev, button, popup) { + $(popup).off('.media', 'form') + } + + MediaManager.prototype.itemsMoved = function() { + this.$el.find('button[data-command="move"]').popup('hide') + + this.afterNavigateBound + } + // EVENT HANDLERS // ============================ @@ -834,11 +921,14 @@ this.setFilter($(ev.currentTarget).data('filter')) break; case 'delete': - this.deleteFiles() + this.deleteItems() break; case 'create-folder': this.createFolder(ev) break; + case 'move': + this.moveItems(ev) + break; } return false @@ -958,8 +1048,9 @@ MediaManager.DEFAULTS = { alias: '', - deleteEmpty: 'Please select files to delete', - deleteConfirm: 'Do you really want to delete the selected file(s)?' + deleteEmpty: 'Please select files to delete.', + deleteConfirm: 'Do you really want to delete the selected file(s)?', + moveEmpty: 'Please select files to move.' } var old = $.fn.mediaManager diff --git a/modules/cms/widgets/mediamanager/assets/less/mediamanager.less b/modules/cms/widgets/mediamanager/assets/less/mediamanager.less index 1cc662dec..ff391fc8d 100644 --- a/modules/cms/widgets/mediamanager/assets/less/mediamanager.less +++ b/modules/cms/widgets/mediamanager/assets/less/mediamanager.less @@ -388,6 +388,10 @@ div[data-control="media-manager"] { display: block; } } + + tr[data-item-type=folder] i.icon-folder { + color: @color-list-hover-bg; + } } div[data-control="selection-marker"] { diff --git a/modules/cms/widgets/mediamanager/partials/_body.htm b/modules/cms/widgets/mediamanager/partials/_body.htm index 70bbe13e6..8d5874b4a 100644 --- a/modules/cms/widgets/mediamanager/partials/_body.htm +++ b/modules/cms/widgets/mediamanager/partials/_body.htm @@ -3,7 +3,9 @@ class="layout" data-alias="alias ?>" data-delete-empty="" - data-delete-confirm=""> + data-delete-confirm="" + data-move-empty="" +> makePartial('toolbar') ?> makePartial('upload-progress') ?> diff --git a/modules/cms/widgets/mediamanager/partials/_move-form.htm b/modules/cms/widgets/mediamanager/partials/_move-form.htm new file mode 100644 index 000000000..b2cc00152 --- /dev/null +++ b/modules/cms/widgets/mediamanager/partials/_move-form.htm @@ -0,0 +1,35 @@ + + + + + \ No newline at end of file