Implemented file and folder renaming

This commit is contained in:
alekseybobkov 2015-03-21 16:14:26 -07:00
parent 331276c244
commit cd0b107a2b
10 changed files with 362 additions and 15 deletions

View File

@ -211,6 +211,79 @@ class MediaLibrary
return $this->getStorageDisk()->put($fullPath, $contents);
}
/**
* Moves a file to another location.
* @param string $oldPath Specifies the original path of the file.
* @param string $newPath Specifies the new path of the file.
* @return boolean
*/
public function moveFile($oldPath, $newPath, $isRename = false)
{
$oldPath = self::validatePath($oldPath);
$fullOldPath = $this->getMediaPath($oldPath);
$newPath = self::validatePath($newPath);
$fullNewPath = $this->getMediaPath($newPath);
return $this->getStorageDisk()->move($fullOldPath, $fullNewPath);
}
/**
* Copies a folder.
* @param string $originalPath Specifies the original path of the folder.
* @param string $newPath Specifies the new path of the folder.
* @return boolean
*/
public function copyFolder($originalPath, $newPath)
{
$disk = $this->getStorageDisk();
$copyDirectory = function($srcPath, $destPath) use (&$copyDirectory, $disk) {
$srcPath = self::validatePath($srcPath);
$fullSrcPath = $this->getMediaPath($srcPath);
$destPath = self::validatePath($destPath);
$fullDestPath = $this->getMediaPath($destPath);
if (!$disk->makeDirectory($fullDestPath))
return false;
$folderContents = $this->scanFolderContents($fullSrcPath);
foreach ($folderContents['folders'] as $dirInfo) {
if (!$copyDirectory($dirInfo->path, $destPath.'/'.basename($dirInfo->path)))
return false;
}
foreach ($folderContents['files'] as $fileInfo) {
$fullFileSrcPath = $this->getMediaPath($fileInfo->path);
if (!$disk->copy($fullFileSrcPath, $fullDestPath.'/'.basename($fileInfo->path)))
return false;
}
return true;
};
return $copyDirectory($originalPath, $newPath);
}
/**
* Moves a folder.
* @param string $originalPath Specifies the original path of the folder.
* @param string $newPath Specifies the new path of the folder.
* @return boolean
*/
public function moveFolder($originalPath, $newPath)
{
if (!$this->copyFolder($originalPath, $newPath))
return false;
$this->deleteFolder($originalPath);
return true;
}
/**
* Resets the Library cache.
*

View File

@ -262,5 +262,6 @@ return [
'no_files_found' => 'No files found by your request.',
'delete_empty' => 'Please select files to delete.',
'delete_confirm' => 'Do you really want to delete the selected file(s)?',
'error_renaming_file' => 'Error renaming file.'
]
];

View File

@ -211,7 +211,7 @@ class MediaManager extends WidgetBase
if (count($filesToDelete) > 0)
$library->deleteFiles($filesToDelete);
MediaLibrary::instance()->resetCache();
$library->resetCache();
$this->prepareVars();
return [
@ -219,6 +219,42 @@ class MediaManager extends WidgetBase
];
}
public function onLoadRenamePopup()
{
$path = Input::get('path');
$path = MediaLibrary::validatePath($path);
$this->vars['originalPath'] = $path;
$this->vars['name'] = basename($path);
$this->vars['listId'] = Input::get('listId');
$this->vars['type'] = Input::get('type');
return $this->makePartial('rename_form');
}
public function onApplyName()
{
$newName = trim(Input::get('name'));
if (!strlen($newName))
throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty'));
if (!$this->validateFileName($newName))
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name'));
$originalPath = Input::get('originalPath');
$originalPath = MediaLibrary::validatePath($originalPath);
$newPath = dirname($originalPath).'/'.$newName;
$type = Input::get('type');
if ($type == MediaLibraryItem::TYPE_FILE)
MediaLibrary::instance()->moveFile($originalPath, $newPath);
else
MediaLibrary::instance()->moveFolder($originalPath, $newPath);
MediaLibrary::instance()->resetCache();
}
//
// Methods for th internal use
//
@ -338,14 +374,18 @@ class MediaManager extends WidgetBase
protected function splitPathToSegments($path)
{
$path = MediaLibrary::validatePath($path, true);
$path = explode('/', ltrim($path, '/'));
$path = ltrim($path, '/');
$result = [];
while (count($path) > 0) {
$folder = array_pop($path);
$result = explode('/', $path);
if (count($result) == 1 && $result[0] == '')
$result = [];
$result[$folder] = implode('/', $path).'/'.$folder;
if (substr($result[$folder], 0, 1) != '/')
$result[$folder] = '/'.$result[$folder];
}
return $result;
return array_reverse($result);
}
/**
@ -618,4 +658,15 @@ class MediaManager extends WidgetBase
die();
}
}
protected function validateFileName($name)
{
if (!preg_match('/^[0-9a-z\.\s_\-]+$/i', $name))
return false;
if (strpos($name, '..') !== false)
return false;
return true;
}
}

View File

@ -60,9 +60,27 @@ div[data-control="media-manager"] .media-list li h4 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 15px;
line-height: 150%;
margin: 15px 0 5px 0;
padding-right: 0;
-webkit-transition: padding 0.1s;
transition: padding 0.1s;
position: relative;
}
div[data-control="media-manager"] .media-list li h4 a {
position: absolute;
right: 0;
top: 0;
font-size: 15px;
color: #2b3e50;
display: none;
}
div[data-control="media-manager"] .media-list li h4 a:hover {
color: #0181b9;
text-decoration: none;
}
div[data-control="media-manager"] .media-list li:hover h4 a {
display: block;
}
div[data-control="media-manager"] .media-list li p.size {
font-size: 12px;
@ -147,6 +165,15 @@ div[data-control="media-manager"] .media-list.list li.selected h4 {
div[data-control="media-manager"] .media-list.list li.selected .icon-container {
border-right-color: #4da7e8 !important;
}
div[data-control="media-manager"] .media-list.list h4 {
padding-right: 15px;
}
div[data-control="media-manager"] .media-list.list h4 a {
right: 15px;
}
div[data-control="media-manager"] .media-list.list li:hover h4 {
padding-right: 35px;
}
div[data-control="media-manager"] .media-list.tiles li {
width: 167px;
margin-bottom: 25px;
@ -200,12 +227,18 @@ div[data-control="media-manager"] .media-list.tiles li.selected .icon-container
div[data-control="media-manager"] .media-list.tiles li.selected h4 {
color: #2581b8;
}
div[data-control="media-manager"] .media-list.tiles li:hover h4 {
padding-right: 20px;
}
div[data-control="media-manager"] .media-list.tiles i.icon-chain-broken {
margin-top: 47px;
}
div[data-control="media-manager"] .media-list.tiles p.size {
margin-bottom: 0;
}
div[data-control="media-manager"] [data-control="sidebar-labels"] {
word-wrap: break-word;
}
div[data-control="media-manager"] .sidebar-image-placeholder-container {
display: table;
width: 100%;
@ -272,6 +305,32 @@ div[data-control="media-manager"] [data-control="item-list"] {
position: relative;
display: table-cell;
}
div[data-control="media-manager"] table.table {
table-layout: fixed;
white-space: nowrap;
}
div[data-control="media-manager"] table.table div.no-wrap-text {
overflow: hidden;
text-overflow: ellipsis;
}
div[data-control="media-manager"] table.table div.item-title {
position: relative;
padding-right: 0;
-webkit-transition: padding 0.1s;
transition: padding 0.1s;
}
div[data-control="media-manager"] table.table div.item-title a {
position: absolute;
right: 0;
top: 0;
display: none;
}
div[data-control="media-manager"] table.table tr:hover div.item-title {
padding-right: 25px;
}
div[data-control="media-manager"] table.table tr:hover div.item-title a {
display: block;
}
div[data-control="media-manager"] div[data-control="selection-marker"] {
position: absolute;
z-index: 50;

View File

@ -40,6 +40,7 @@
this.updateSearchResultsBound = this.updateSearchResults.bind(this)
this.releaseNavigationAjaxBound = this.releaseNavigationAjax.bind(this)
this.deleteConfirmationBound = this.deleteConfirmation.bind(this)
this.refreshBound = this.refresh.bind(this)
// State properties
this.selectTimer = null
@ -83,6 +84,7 @@
this.updateSearchResultsBound = null
this.releaseNavigationAjaxBound = null
this.deleteConfirmationBound = null
this.refreshBound = null
this.sidebarPreviewElement = null
this.itemListElement = null
@ -111,6 +113,7 @@
this.$el.on('click.item', '[data-type="media-item"]', this.itemClickHandler)
this.$el.on('change', '[data-control="sorting"]', this.sortingChangedHandler)
this.$el.on('keyup', '[data-control="search"]', this.searchChangedHandler)
this.$el.on('mediarefresh', this.refreshBound)
if (this.itemListElement)
this.itemListElement.addEventListener('mousedown', this.listMouseDownHandler)
@ -760,6 +763,7 @@
break;
case 'set-filter':
this.setFilter($(ev.currentTarget).data('filter'))
break;
case 'delete':
this.deleteFiles()
break;
@ -769,7 +773,8 @@
}
MediaManager.prototype.onItemClick = function(ev) {
if (ev.currentTarget.hasAttribute('data-root'))
// Don't select "Go up" folders and don't select items when the rename icon is clicked
if (ev.currentTarget.hasAttribute('data-root') || ev.target.tagName == 'I')
return
this.selectItem(ev.currentTarget, ev.shiftKey)

View File

@ -119,9 +119,30 @@ div[data-control="media-manager"] {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 15px;
line-height: 150%;
margin: 15px 0 5px 0;
padding-right: 0;
.transition(padding 0.1s);
position: relative;
a {
position: absolute;
right: 0;
top: 0;
font-size: 15px;
color: #2b3e50;
display: none;
&:hover {
color: @color-link;
text-decoration: none;
}
}
}
&:hover h4 a {
display: block;
}
p.size {
@ -205,6 +226,18 @@ div[data-control="media-manager"] {
li.selected {
.media-selected-list();
}
h4 {
padding-right: 15px;
a {
right: 15px;
}
}
li:hover h4 {
padding-right: 35px;
}
}
&.tiles {
@ -247,6 +280,10 @@ div[data-control="media-manager"] {
.media-selected-tiles();
}
li:hover h4 {
padding-right: 20px;
}
i.icon-chain-broken {
margin-top: 47px;
}
@ -257,6 +294,10 @@ div[data-control="media-manager"] {
}
}
[data-control="sidebar-labels"] {
word-wrap: break-word;
}
.sidebar-image-placeholder-container {
display: table;
width: 100%;
@ -318,6 +359,37 @@ div[data-control="media-manager"] {
display: table-cell;
}
table.table {
table-layout: fixed;
white-space: nowrap;
div.no-wrap-text {
overflow: hidden;
text-overflow: ellipsis;
}
div.item-title {
position: relative;
padding-right: 0;
.transition(padding 0.1s);
a {
position: absolute;
right: 0;
top: 0;
display: none;
}
}
tr:hover div.item-title{
padding-right: 25px;
a {
display: block;
}
}
}
div[data-control="selection-marker"] {
position: absolute;
z-index: 50;

View File

@ -2,9 +2,9 @@
<li class="root"><a href="#" data-type="media-item" data-item-type="folder" data-path="/" data-clear-search="true"><?= e(trans('cms::lang.media.library')) ?></a></li>
<?php if (!$searchMode): ?>
<?php foreach ($pathSegments as $segment): ?>
<?php if ($segment != '/'): ?>
<li><a href="#" data-type="media-item" data-item-type="folder" data-path="<?= e($segment) ?>"><?= basename($segment) ?></a></li>
<?php foreach ($pathSegments as $folder=>$path): ?>
<?php if ($path != '/'): ?>
<li><a href="#" data-type="media-item" data-item-type="folder" data-path="<?= e($path) ?>"><?= basename($folder) ?></a></li>
<?php endif ?>
<?php endforeach?>
<?php else: ?>

View File

@ -1,3 +1,7 @@
<?php
$listElementId = $this->getId('item-list');
?>
<ul class="media-list <?= $listClass ?>">
<?php if (count($items) > 0 || !$isRootFolder): ?>
<?php if (!$isRootFolder && !$searchMode): ?>
@ -28,7 +32,17 @@
<?= $this->makePartial('item-icon', ['itemType'=>$itemType, 'item'=>$item]) ?>
<div class="info">
<h4 title="<?= e(basename($item->path)) ?>"><?= e(basename($item->path)) ?></h4>
<h4 title="<?= e(basename($item->path)) ?>">
<?= e(basename($item->path)) ?>
<a
href="#"
data-rename
data-control="popup"
data-request-data="path: '<?= e($item->path) ?>', listId: '<?= $listElementId ?>', type: '<?= $item->type ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
><i class="icon-terminal"></i></a>
</h4>
<p class="size"><?= e($item->sizeToString()) ?></p>
</div>
</li>

View File

@ -1,4 +1,12 @@
<?php
$listElementId = $this->getId('item-list');
?>
<table class="table data">
<col />
<col width="100px" />
<col width="100px" />
<tbody class="icons clickable">
<?php if (count($items) > 0 || !$isRootFolder): ?>
<?php if (!$isRootFolder && !$searchMode): ?>
@ -23,11 +31,25 @@
data-document-type="<?= e($itemType) ?>"
data-folder="<?= e(dirname($item->path)) ?>"
>
<td><i class="<?= $this->itemTypeToIconClass($item, $itemType) ?>"></i> <?= e(basename($item->path)) ?></td>
<td>
<div class="item-title no-wrap-text">
<i class="<?= $this->itemTypeToIconClass($item, $itemType) ?>"></i> <?= e(basename($item->path)) ?>
<a
href="#"
data-rename
data-control="popup"
data-request-data="path: '<?= e($item->path) ?>', listId: '<?= $listElementId ?>', type: '<?= $item->type ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
><i class="icon-terminal"></i></a>
</div>
</td>
<td><?= e($item->sizeToString()) ?></td>
<td><?= e($item->lastModifiedAsString()) ?></td>
<?php if ($searchMode): ?>
<td><?= e(dirname($item->path)) ?></td>
<td title="<?= e(dirname($item->path)) ?>">
<div class="no-wrap-text"><?= e(dirname($item->path)) ?></div>
</td>
<?php endif ?>
</tr>
<?php endforeach ?>

View File

@ -0,0 +1,50 @@
<?= Form::ajax($this->getEventHandler('onApplyName'), [
'success' => "\$el.trigger('close.oc.popup'); \$('#".$listId."').trigger('mediarefresh');",
'data-stripe-load-indicator' => 1,
'id' => 'media-rename-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.rename_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.rename_new_name')) ?></label>
<input type="text" class="form-control" name="name" value="<?= e($name) ?>" />
<input type="hidden" name="originalName" value="<?= e($name) ?>">
<input type="hidden" name="type" value="<?= e($type) ?>">
</div>
<input type="hidden" name="originalPath" value="<?= e($originalPath) ?>" />
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<script>
setTimeout(
function(){ $('#media-rename-popup-form input.form-control').focus() },
310
)
$('#media-rename-popup-form').on('oc.beforeRequest', function(ev){
var originalName = $('#media-rename-popup-form [name=originalName]').val(),
newName = $.trim($('#media-rename-popup-form [name=name]').val())
if (originalName == newName || newName.length == 0) {
alert('Please enter a new name')
ev.preventDefault()
}
})
</script>
<?= Form::close() ?>