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 Illuminate\Support\Facades\DB;
|
||||||
use Oxbow\Http\Requests;
|
use Oxbow\Http\Requests;
|
||||||
use Oxbow\Image;
|
use Oxbow\Image;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use RegexIterator;
|
||||||
|
|
||||||
class ImageController extends Controller
|
class ImageController extends Controller
|
||||||
{
|
{
|
||||||
@ -71,7 +74,7 @@ class ImageController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function getAll($page = 0)
|
public function getAll($page = 0)
|
||||||
{
|
{
|
||||||
$pageSize = 25;
|
$pageSize = 30;
|
||||||
$images = DB::table('images')->orderBy('created_at', 'desc')
|
$images = DB::table('images')->orderBy('created_at', 'desc')
|
||||||
->skip($page*$pageSize)->take($pageSize)->get();
|
->skip($page*$pageSize)->take($pageSize)->get();
|
||||||
foreach($images as $image) {
|
foreach($images as $image) {
|
||||||
@ -146,5 +149,44 @@ class ImageController extends Controller
|
|||||||
return response()->json($this->image);
|
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
|
// Image routes
|
||||||
Route::get('/images/all', 'ImageController@getAll');
|
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/all/{page}', 'ImageController@getAll');
|
||||||
Route::get('/images/{any}', 'ImageController@getImage')->where('any', '.*');
|
Route::get('/images/{any}', 'ImageController@getImage')->where('any', '.*');
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class Image extends Model
|
class Image extends Model
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected $fillable = ['name'];
|
||||||
|
|
||||||
public function getFilePath()
|
public function getFilePath()
|
||||||
{
|
{
|
||||||
return storage_path() . $this->url;
|
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() {
|
(function() {
|
||||||
|
|
||||||
var ImageManager = new Vue({
|
var ImageManager = new Vue({
|
||||||
@ -56,7 +82,7 @@
|
|||||||
var dblClickTime = 380;
|
var dblClickTime = 380;
|
||||||
var cTime = (new Date()).getTime();
|
var cTime = (new Date()).getTime();
|
||||||
var timeDiff = cTime - this.cClickTime;
|
var timeDiff = cTime - this.cClickTime;
|
||||||
if(this.cClickTime !== 0 && timeDiff < dblClickTime) {
|
if(this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
|
||||||
// DoubleClick
|
// DoubleClick
|
||||||
if(this.callback) {
|
if(this.callback) {
|
||||||
this.callback(image);
|
this.callback(image);
|
||||||
@ -68,6 +94,13 @@
|
|||||||
this.cClickTime = cTime;
|
this.cClickTime = cTime;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectButtonClick: function() {
|
||||||
|
if(this.callback) {
|
||||||
|
this.callback(this.selectedImage);
|
||||||
|
}
|
||||||
|
this.hide();
|
||||||
|
},
|
||||||
|
|
||||||
show: function(callback) {
|
show: function(callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.$$.overlay.style.display = 'block';
|
this.$$.overlay.style.display = 'block';
|
||||||
@ -81,6 +114,34 @@
|
|||||||
|
|
||||||
hide: function() {
|
hide: function() {
|
||||||
this.$$.overlay.style.display = 'none';
|
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;
|
margin-bottom: $-s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
.text-pos, .text-neg {
|
||||||
|
padding: $-xs 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.inline-input-style {
|
.inline-input-style {
|
||||||
border: 2px dotted #BBB;
|
border: 2px dotted #BBB;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -82,7 +82,7 @@ hr {
|
|||||||
&.faded {
|
&.faded {
|
||||||
background-image: linear-gradient(to right, #FFF, #e3e0e0 20%, #e3e0e0 80%, #FFF);
|
background-image: linear-gradient(to right, #FFF, #e3e0e0 20%, #e3e0e0 80%, #FFF);
|
||||||
}
|
}
|
||||||
&.margin-top {
|
&.margin-top, &.even {
|
||||||
margin-top: $-l;
|
margin-top: $-l;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,4 +227,11 @@ ul {
|
|||||||
|
|
||||||
.list > * {
|
.list > * {
|
||||||
display: block;
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icons
|
||||||
|
*/
|
||||||
|
i {
|
||||||
|
padding-right: $-xs;
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
.image-manager-body {
|
.image-manager-body {
|
||||||
background-color: rgb(37, 37, 37);
|
background-color: #FFF;
|
||||||
max-width: 90%;
|
|
||||||
max-height: 90%;
|
max-height: 90%;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
height: 90%;
|
height: 90%;
|
||||||
@ -9,18 +8,7 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.image-manager-list img {
|
max-width: 1340px;
|
||||||
border-radius: 0;
|
|
||||||
float: left;
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
&.selected {
|
|
||||||
border: 3px solid #EEE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -28,34 +16,48 @@
|
|||||||
z-index: 999;
|
z-index: 999;
|
||||||
display: flex;
|
display: flex;
|
||||||
p, h1, h2, h3, h4, label, input {
|
p, h1, h2, h3, h4, label, input {
|
||||||
color: #EEE;
|
color: #444;
|
||||||
}
|
}
|
||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#image-manager .dropzone-container {
|
#image-manager .dropzone-container {
|
||||||
height: 100px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border: 3px dashed #DDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
#container {
|
.image-manager-bottom {
|
||||||
height: 90vh;
|
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 {
|
#image-manager .load-more {
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #404040;
|
background-color: #EEE;
|
||||||
margin: 1px;
|
padding: $-s $-m;
|
||||||
color: #FFF;
|
color: #AAA;
|
||||||
line-height: 140px;
|
clear: both;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-manager-sidebar {
|
.image-manager-sidebar {
|
||||||
@ -75,6 +77,7 @@
|
|||||||
.image-manager-list {
|
.image-manager-list {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-manager-content {
|
.image-manager-content {
|
||||||
@ -93,19 +96,13 @@
|
|||||||
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
||||||
*/
|
*/
|
||||||
.dz-message {
|
.dz-message {
|
||||||
font-size: 1.6em;
|
font-size: 1.4em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 90px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: $-xl $-m;
|
||||||
transition: all ease-in-out 120ms;
|
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 {
|
.dz-drag-hover .dz-message {
|
||||||
background-color: rgb(16, 126, 210);
|
background-color: rgb(16, 126, 210);
|
||||||
@ -131,22 +128,10 @@
|
|||||||
transform: translateY(0px); } }
|
transform: translateY(0px); } }
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: scale(1);
|
|
||||||
-moz-transform: scale(1);
|
|
||||||
-ms-transform: scale(1);
|
|
||||||
-o-transform: scale(1);
|
|
||||||
transform: scale(1); }
|
transform: scale(1); }
|
||||||
10% {
|
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); }
|
transform: scale(1.1); }
|
||||||
20% {
|
20% {
|
||||||
-webkit-transform: scale(1);
|
|
||||||
-moz-transform: scale(1);
|
|
||||||
-ms-transform: scale(1);
|
|
||||||
-o-transform: scale(1);
|
|
||||||
transform: scale(1); } }
|
transform: scale(1); } }
|
||||||
.dropzone, .dropzone * {
|
.dropzone, .dropzone * {
|
||||||
box-sizing: border-box; }
|
box-sizing: border-box; }
|
||||||
@ -171,10 +156,6 @@
|
|||||||
.dz-preview.dz-image-preview {
|
.dz-preview.dz-image-preview {
|
||||||
background: white; }
|
background: white; }
|
||||||
.dz-preview.dz-image-preview .dz-details {
|
.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; }
|
transition: opacity 0.2s linear; }
|
||||||
.dz-preview .dz-remove {
|
.dz-preview .dz-remove {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -217,12 +198,6 @@
|
|||||||
padding: 0 0.4em;
|
padding: 0 0.4em;
|
||||||
border-radius: 3px; }
|
border-radius: 3px; }
|
||||||
.dz-preview:hover .dz-image img {
|
.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); }
|
filter: blur(8px); }
|
||||||
.dz-preview .dz-image {
|
.dz-preview .dz-image {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
@import "buttons";
|
@import "buttons";
|
||||||
@import "forms";
|
@import "forms";
|
||||||
@import "tables";
|
@import "tables";
|
||||||
|
@import "animations";
|
||||||
@import "tinymce";
|
@import "tinymce";
|
||||||
@import "image-manager";
|
@import "image-manager";
|
||||||
|
|
||||||
|
@ -3,19 +3,42 @@
|
|||||||
<div class="overlay" v-el="overlay" v-on="click: overlayClick" style="display:none;">
|
<div class="overlay" v-el="overlay" v-on="click: overlayClick" style="display:none;">
|
||||||
<div class="image-manager-body">
|
<div class="image-manager-body">
|
||||||
<div class="image-manager-content">
|
<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 class="image-manager-list">
|
||||||
<div v-repeat="image: images">
|
<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>
|
||||||
<div class="load-more" v-show="hasMore" v-on="click: fetchData">Load More</div>
|
<div class="load-more" v-show="hasMore" v-on="click: fetchData">Load More</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="neg button image-manager-close" v-on="click: hide()">x</button>
|
||||||
<div class="image-manager-sidebar">
|
<div class="image-manager-sidebar">
|
||||||
<button class="neg button image-manager-close" v-on="click: hide()">x</button>
|
<h2 v-el="imageTitle">Images</h2>
|
||||||
<h2>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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user