mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added image name editing & deleting
This commit is contained in:
parent
b0808a1c24
commit
e5a372ffbd
@ -10,6 +10,9 @@ use Intervention\Image\Facades\Image as ImageTool;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Oxbow\Http\Requests;
|
||||
use Oxbow\Image;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RegexIterator;
|
||||
|
||||
class ImageController extends Controller
|
||||
{
|
||||
@ -71,7 +74,7 @@ class ImageController extends Controller
|
||||
*/
|
||||
public function getAll($page = 0)
|
||||
{
|
||||
$pageSize = 25;
|
||||
$pageSize = 30;
|
||||
$images = DB::table('images')->orderBy('created_at', 'desc')
|
||||
->skip($page*$pageSize)->take($pageSize)->get();
|
||||
foreach($images as $image) {
|
||||
@ -146,5 +149,44 @@ class ImageController extends Controller
|
||||
return response()->json($this->image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update image details
|
||||
* @param $imageId
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update($imageId, Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|min:2|string'
|
||||
]);
|
||||
$image = $this->image->findOrFail($imageId);
|
||||
$image->fill($request->all());
|
||||
$image->save();
|
||||
return response()->json($this->image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an image and all thumbnail/image files
|
||||
* @param $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$image = $this->image->findOrFail($id);
|
||||
|
||||
// Delete files
|
||||
$folder = public_path() . dirname($image->url);
|
||||
$pattern = '/' . preg_quote(basename($image->url)). '/';
|
||||
$dir = new RecursiveDirectoryIterator($folder);
|
||||
$ite = new RecursiveIteratorIterator($dir);
|
||||
$files = new RegexIterator($ite, $pattern, RegexIterator::ALL_MATCHES);
|
||||
foreach($files as $path => $file) {
|
||||
unlink($path);
|
||||
}
|
||||
$image->delete();
|
||||
return response()->json('Image Deleted');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -68,6 +68,8 @@ Route::group(['middleware' => 'auth'], function() {
|
||||
|
||||
// Image routes
|
||||
Route::get('/images/all', 'ImageController@getAll');
|
||||
Route::put('/images/update/{imageId}', 'ImageController@update');
|
||||
Route::delete('/images/{imageId}', 'ImageController@destroy');
|
||||
Route::get('/images/all/{page}', 'ImageController@getAll');
|
||||
Route::get('/images/{any}', 'ImageController@getImage')->where('any', '.*');
|
||||
|
||||
|
@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Image extends Model
|
||||
{
|
||||
|
||||
protected $fillable = ['name'];
|
||||
|
||||
public function getFilePath()
|
||||
{
|
||||
return storage_path() . $this->url;
|
||||
|
@ -1,4 +1,30 @@
|
||||
|
||||
jQuery.fn.showSuccess = function(message) {
|
||||
var elem = $(this);
|
||||
var success = $('<div class="text-pos" style="display:none;"><i class="zmdi zmdi-check-circle"></i>'+message+'</div>');
|
||||
elem.after(success);
|
||||
success.slideDown(400, function() {
|
||||
setTimeout(function() {success.slideUp(400, function() {
|
||||
success.remove();
|
||||
})}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
jQuery.fn.showFailure = function(messageMap) {
|
||||
var elem = $(this);
|
||||
$.each(messageMap, function(key, messages) {
|
||||
var input = elem.find('[name="'+key+'"]').last();
|
||||
var fail = $('<div class="text-neg" style="display:none;"><i class="zmdi zmdi-alert-circle"></i>'+messages.join("\n")+'</div>');
|
||||
input.after(fail);
|
||||
fail.slideDown(400, function() {
|
||||
setTimeout(function() {fail.slideUp(400, function() {
|
||||
fail.remove();
|
||||
})}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
var ImageManager = new Vue({
|
||||
@ -56,7 +82,7 @@
|
||||
var dblClickTime = 380;
|
||||
var cTime = (new Date()).getTime();
|
||||
var timeDiff = cTime - this.cClickTime;
|
||||
if(this.cClickTime !== 0 && timeDiff < dblClickTime) {
|
||||
if(this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
|
||||
// DoubleClick
|
||||
if(this.callback) {
|
||||
this.callback(image);
|
||||
@ -68,6 +94,13 @@
|
||||
this.cClickTime = cTime;
|
||||
},
|
||||
|
||||
selectButtonClick: function() {
|
||||
if(this.callback) {
|
||||
this.callback(this.selectedImage);
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
|
||||
show: function(callback) {
|
||||
this.callback = callback;
|
||||
this.$$.overlay.style.display = 'block';
|
||||
@ -81,6 +114,34 @@
|
||||
|
||||
hide: function() {
|
||||
this.$$.overlay.style.display = 'none';
|
||||
},
|
||||
|
||||
saveImageDetails: function(e) {
|
||||
e.preventDefault();
|
||||
var _this = this;
|
||||
var form = $(_this.$$.imageForm);
|
||||
$.ajax('/images/update/' + _this.selectedImage.id, {
|
||||
method: 'PUT',
|
||||
data: form.serialize()
|
||||
}).done(function() {
|
||||
form.showSuccess('Image name updated');
|
||||
}).fail(function(jqXHR) {
|
||||
form.showFailure(jqXHR.responseJSON);
|
||||
})
|
||||
},
|
||||
|
||||
deleteImage: function(e) {
|
||||
e.preventDefault();
|
||||
var _this = this;
|
||||
var form = $(_this.$$.imageDeleteForm);
|
||||
$.ajax('/images/' + _this.selectedImage.id, {
|
||||
method: 'DELETE',
|
||||
data: form.serialize()
|
||||
}).done(function() {
|
||||
_this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
|
||||
_this.selectedImage = false;
|
||||
$(_this.$$.imageTitle).showSuccess('Image Deleted');
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
17
resources/assets/sass/_animations.scss
Normal file
17
resources/assets/sass/_animations.scss
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
.anim.fadeIn {
|
||||
opacity: 0;
|
||||
animation-name: fadeIn;
|
||||
animation-duration: 160ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
@ -45,6 +45,12 @@ input[type="text"], input[type="number"], input[type="email"], input[type="searc
|
||||
margin-bottom: $-s;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
.text-pos, .text-neg {
|
||||
padding: $-xs 0;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-input-style {
|
||||
border: 2px dotted #BBB;
|
||||
display: block;
|
||||
|
@ -82,7 +82,7 @@ hr {
|
||||
&.faded {
|
||||
background-image: linear-gradient(to right, #FFF, #e3e0e0 20%, #e3e0e0 80%, #FFF);
|
||||
}
|
||||
&.margin-top {
|
||||
&.margin-top, &.even {
|
||||
margin-top: $-l;
|
||||
}
|
||||
}
|
||||
@ -227,4 +227,11 @@ ul {
|
||||
|
||||
.list > * {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icons
|
||||
*/
|
||||
i {
|
||||
padding-right: $-xs;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
.image-manager-body {
|
||||
background-color: rgb(37, 37, 37);
|
||||
max-width: 90%;
|
||||
background-color: #FFF;
|
||||
max-height: 90%;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
@ -9,18 +8,7 @@
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
.image-manager-list img {
|
||||
border-radius: 0;
|
||||
float: left;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border: 1px solid transparent;
|
||||
&.selected {
|
||||
border: 3px solid #EEE;
|
||||
}
|
||||
}
|
||||
max-width: 1340px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
@ -28,34 +16,48 @@
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
p, h1, h2, h3, h4, label, input {
|
||||
color: #EEE;
|
||||
color: #444;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
#image-manager .dropzone-container {
|
||||
height: 100px;
|
||||
position: relative;
|
||||
border: 3px dashed #DDD;
|
||||
}
|
||||
|
||||
#container {
|
||||
height: 90vh;
|
||||
.image-manager-bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.image-manager-list img {
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
float: left;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: (100%/6);
|
||||
height: auto;
|
||||
border: 1px solid #FFF;
|
||||
transition: all cubic-bezier(.4,0,1,1) 160ms;
|
||||
&.selected {
|
||||
transform: scale3d(0.92, 0.92, 0.92);
|
||||
}
|
||||
}
|
||||
|
||||
#image-manager .load-more {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: block;
|
||||
float: left;
|
||||
text-align: center;
|
||||
background-color: #404040;
|
||||
margin: 1px;
|
||||
color: #FFF;
|
||||
line-height: 140px;
|
||||
background-color: #EEE;
|
||||
padding: $-s $-m;
|
||||
color: #AAA;
|
||||
clear: both;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.image-manager-sidebar {
|
||||
@ -75,6 +77,7 @@
|
||||
.image-manager-list {
|
||||
overflow-y: scroll;
|
||||
flex: 1;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.image-manager-content {
|
||||
@ -93,19 +96,13 @@
|
||||
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
||||
*/
|
||||
.dz-message {
|
||||
font-size: 1.6em;
|
||||
font-size: 1.4em;
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
line-height: 90px;
|
||||
cursor: pointer;
|
||||
padding: $-xl $-m;
|
||||
transition: all ease-in-out 120ms;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
max-width: 400px;
|
||||
width: 400px;
|
||||
margin-left: -200px;
|
||||
}
|
||||
.dz-drag-hover .dz-message {
|
||||
background-color: rgb(16, 126, 210);
|
||||
@ -131,22 +128,10 @@
|
||||
transform: translateY(0px); } }
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
10% {
|
||||
-webkit-transform: scale(1.1);
|
||||
-moz-transform: scale(1.1);
|
||||
-ms-transform: scale(1.1);
|
||||
-o-transform: scale(1.1);
|
||||
transform: scale(1.1); }
|
||||
20% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); } }
|
||||
.dropzone, .dropzone * {
|
||||
box-sizing: border-box; }
|
||||
@ -171,10 +156,6 @@
|
||||
.dz-preview.dz-image-preview {
|
||||
background: white; }
|
||||
.dz-preview.dz-image-preview .dz-details {
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-ms-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear; }
|
||||
.dz-preview .dz-remove {
|
||||
font-size: 14px;
|
||||
@ -217,12 +198,6 @@
|
||||
padding: 0 0.4em;
|
||||
border-radius: 3px; }
|
||||
.dz-preview:hover .dz-image img {
|
||||
-webkit-transform: scale(1.05, 1.05);
|
||||
-moz-transform: scale(1.05, 1.05);
|
||||
-ms-transform: scale(1.05, 1.05);
|
||||
-o-transform: scale(1.05, 1.05);
|
||||
transform: scale(1.05, 1.05);
|
||||
-webkit-filter: blur(8px);
|
||||
filter: blur(8px); }
|
||||
.dz-preview .dz-image {
|
||||
border-radius: 4px;
|
||||
|
@ -8,6 +8,7 @@
|
||||
@import "buttons";
|
||||
@import "forms";
|
||||
@import "tables";
|
||||
@import "animations";
|
||||
@import "tinymce";
|
||||
@import "image-manager";
|
||||
|
||||
|
@ -3,19 +3,42 @@
|
||||
<div class="overlay" v-el="overlay" v-on="click: overlayClick" style="display:none;">
|
||||
<div class="image-manager-body">
|
||||
<div class="image-manager-content">
|
||||
<div class="dropzone-container" v-el="dropZone">
|
||||
<div class="dz-message">Drop files or click here to upload</div>
|
||||
</div>
|
||||
<div class="image-manager-list">
|
||||
<div v-repeat="image: images">
|
||||
<img v-class="selected: (image==selectedImage)" v-attr="src: image.thumbnail" v-on="click: imageClick(image)" alt="@{{image.name}}">
|
||||
<img class="anim fadeIn"
|
||||
v-class="selected: (image==selectedImage)"
|
||||
v-attr="src: image.thumbnail, alt: image.name, title: image.name"
|
||||
v-on="click: imageClick(image)"
|
||||
v-style="animation-delay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'">
|
||||
</div>
|
||||
<div class="load-more" v-show="hasMore" v-on="click: fetchData">Load More</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="neg button image-manager-close" v-on="click: hide()">x</button>
|
||||
<div class="image-manager-sidebar">
|
||||
<button class="neg button image-manager-close" v-on="click: hide()">x</button>
|
||||
<h2>Images</h2>
|
||||
<h2 v-el="imageTitle">Images</h2>
|
||||
<hr class="even">
|
||||
<div class="dropzone-container" v-el="dropZone">
|
||||
<div class="dz-message">Drop files or click here to upload</div>
|
||||
</div>
|
||||
<div class="image-manager-details anim fadeIn" v-show="selectedImage">
|
||||
<hr class="even">
|
||||
<form v-on="submit: saveImageDetails" v-el="imageForm">
|
||||
{{ csrf_field() }}
|
||||
<div class="form-group">
|
||||
<label for="name">Image Name</label>
|
||||
<input type="text" id="name" name="name" v-model="selectedImage.name">
|
||||
</div>
|
||||
</form>
|
||||
<hr class="even">
|
||||
<form v-on="submit: deleteImage" v-el="imageDeleteForm">
|
||||
{{ csrf_field() }}
|
||||
<button class="button neg"><i class="zmdi zmdi-delete"></i>Delete Image</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="image-manager-bottom">
|
||||
<button class="button pos anim fadeIn" v-show="selectedImage" v-on="click:selectButtonClick"><i class="zmdi zmdi-square-right"></i>Select Image</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user