Improved sidebar and selection styling of image manager.
Allowed image manager imageType to be changed on open.
Created models for image revisions.
}
/**
- * Deletes an image and all thumbnail/image files
+ * Show the usage of an image on pages.
* @param EntityRepo $entityRepo
- * @param Request $request
+ * @param $id
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function usage(EntityRepo $entityRepo, $id)
+ {
+ $image = $this->imageRepo->getById($id);
+ $pageSearch = $entityRepo->searchForImage($image->url);
+ return response()->json($pageSearch);
+ }
+
+ /**
+ * Deletes an image and all thumbnail/image files
* @param int $id
* @return \Illuminate\Http\JsonResponse
*/
- public function destroy(EntityRepo $entityRepo, Request $request, $id)
+ public function destroy($id)
{
$image = $this->imageRepo->getById($id);
$this->checkOwnablePermission('image-delete', $image);
- // Check if this image is used on any pages
- $isForced = in_array($request->get('force', ''), [true, 'true']);
- if (!$isForced) {
- $pageSearch = $entityRepo->searchForImage($image->url);
- if ($pageSearch !== false) {
- return response()->json($pageSearch, 400);
- }
- }
-
$this->imageRepo->destroyImage($image);
return response()->json(trans('components.images_deleted'));
}
/**
* Get a thumbnail for this image.
- * @param int $width
- * @param int $height
+ * @param int $width
+ * @param int $height
* @param bool|false $keepRatio
* @return string
+ * @throws \Exception
*/
public function getThumb($width, $height, $keepRatio = false)
{
return Images::getThumbnail($this, $width, $height, $keepRatio);
}
+
+ /**
+ * Get the revisions for this image.
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function revisions()
+ {
+ return $this->hasMany(ImageRevision::class);
+ }
}
--- /dev/null
+<?php
+
+namespace BookStack;
+
+use Illuminate\Database\Eloquent\Model;
+
+class ImageRevision extends Model
+{
+ /**
+ * Relation for the user that created this entity.
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function createdBy()
+ {
+ return $this->belongsTo(User::class, 'created_by');
+ }
+
+ /**
+ * Get the image that this is a revision of.
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function image()
+ {
+ return $this->belongsTo(Image::class);
+ }
+}
--- /dev/null
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateImageRevisionsTable extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('image_revisions', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('image_id');
+ $table->string('path');
+ $table->string('url');
+ $table->integer('created_by');
+
+ $table->index('image_id');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('image_revisions');
+ }
+}
function drawIoPlugin() {
- const drawIoUrl = 'https://www.draw.io/?embed=1&ui=atlas&spin=1&proto=json';
- let iframe = null;
let pageEditor = null;
let currentNode = null;
return node.hasAttribute('drawio-diagram');
}
+ function showDrawingManager(mceEditor, selectedNode = null) {
+ // TODO - Handle how image manager links in.
+ // Show image manager
+ window.ImageManager.show(function (image) {
+
+
+ // // Replace the actively selected content with the linked image
+ // let html = `<a href="${image.url}" target="_blank">`;
+ // html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
+ // html += '</a>';
+ // win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
+ }, 'drawio');
+ }
+
function showDrawingEditor(mceEditor, selectedNode = null) {
pageEditor = mceEditor;
currentNode = selectedNode;
window.tinymce.PluginManager.add('drawio', function(editor, url) {
editor.addCommand('drawio', () => {
- showDrawingEditor(editor);
+ let selectedNode = editor.selection.getNode();
+ if (isDrawing(selectedNode)) {
+ showDrawingManager(editor, selectedNode);
+ } else {
+ showDrawingEditor(editor);
+ }
});
editor.addButton('drawio', {
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
html += '</a>';
win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
- });
+ }, 'gallery');
}
},
imageUpdateSuccess: false,
imageDeleteSuccess: false,
+ deleteConfirm: false,
};
const methods = {
- show(providedCallback) {
+ show(providedCallback, imageType = null) {
callback = providedCallback;
this.showing = true;
this.$el.children[0].components.overlay.show();
// Get initial images if they have not yet been loaded in.
- if (dataLoaded) return;
+ if (dataLoaded && imageType === this.imageType) return;
+ if (imageType) {
+ this.imageType = imageType;
+ this.resetState();
+ }
this.fetchData();
dataLoaded = true;
},
},
setView(viewName) {
+ this.view = viewName;
+ this.resetState();
+ this.fetchData();
+ },
+
+ resetState() {
this.cancelSearch();
this.images = [];
this.hasMore = false;
+ this.deleteConfirm = false;
page = 0;
- this.view = viewName;
- baseUrl = window.baseUrl(`/images/${this.imageType}/${viewName}/`);
- this.fetchData();
+ baseUrl = window.baseUrl(`/images/${this.imageType}/${this.view}/`);
},
searchImages() {
this.callbackAndHide(image);
} else {
this.selectedImage = image;
+ this.deleteConfirm = false;
this.dependantPages = false;
}
},
deleteImage() {
- let force = this.dependantPages !== false;
- let url = window.baseUrl('/images/' + this.selectedImage.id);
- if (force) url += '?force=true';
- this.$http.delete(url).then(response => {
+
+ if (!this.deleteConfirm) {
+ let url = window.baseUrl(`/images/usage/${this.selectedImage.id}`);
+ this.$http.get(url).then(resp => {
+ this.dependantPages = resp.data;
+ }).catch(console.error).then(() => {
+ this.deleteConfirm = true;
+ });
+ return;
+ }
+
+ this.$http.delete(`/images/${this.selectedImage.id}`).then(resp => {
this.images.splice(this.images.indexOf(this.selectedImage), 1);
this.selectedImage = false;
this.$events.emit('success', trans('components.image_delete_success'));
- }).catch(error=> {
- if (error.response.status === 400) {
- this.dependantPages = error.response.data;
- }
+ this.deleteConfirm = false;
});
},
.dropzone-container {
position: relative;
- border: 3px dashed #DDD;
+ background-color: #EEE;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%23a9a9a9' fill-opacity='0.52' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");
}
.image-manager-list .image {
transition: all cubic-bezier(.4, 0, 1, 1) 160ms;
overflow: hidden;
&.selected {
- transform: scale3d(0.92, 0.92, 0.92);
- border: 1px solid #444;
+ //transform: scale3d(0.92, 0.92, 0.92);
+ border: 4px solid #FFF;
+ overflow: hidden;
+ border-radius: 8px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
}
img {
.image-manager-sidebar {
width: 300px;
margin-left: 1px;
- padding: $-m $-l;
overflow-y: auto;
overflow-x: hidden;
border-left: 1px solid #DDD;
+ .inner {
+ padding: $-m;
+ }
+ img {
+ max-width: 100%;
+ max-height: 200px;
+ display: block;
+ margin: 0 auto $-m auto;
+ box-shadow: $bs-light;
+ }
.dropzone-container {
- margin-top: $-m;
+ border-bottom: 1px solid #DDD;
}
}
*/
.dz-message {
- font-size: 1.2em;
- line-height: 1.1;
+ font-size: 1em;
+ line-height: 2.35;
font-style: italic;
- color: #aaa;
+ color: #888;
text-align: center;
cursor: pointer;
padding: $-l $-m;
}
input {
flex: 5;
+ padding: $-xs $-s;
&:focus, &:active {
outline: 0;
}
'image_uploaded' => 'Hochgeladen am :uploadedDate',
'image_load_more' => 'Mehr',
'image_image_name' => 'Bildname',
- 'image_delete_confirm' => 'Dieses Bild wird auf den folgenden Seiten benutzt. Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild wirklich entfernen möchten.',
+ 'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ',
+ 'image_delete_confirm' => 'Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild wirklich entfernen möchten.',
'image_select_image' => 'Bild auswählen',
'image_dropzone' => 'Ziehen Sie Bilder hierher oder klicken Sie, um ein Bild auszuwählen',
'images_deleted' => 'Bilder gelöscht',
'image_uploaded' => 'Uploaded :uploadedDate',
'image_load_more' => 'Load More',
'image_image_name' => 'Image Name',
- 'image_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.',
+ 'image_delete_used' => 'This image is used in the pages below.',
+ 'image_delete_confirm' => 'Click delete again to confirm you want to delete this image.',
'image_select_image' => 'Select Image',
'image_dropzone' => 'Drop images or click here to upload',
'images_deleted' => 'Images Deleted',
'image_uploaded' => 'Subido el :uploadedDate',
'image_load_more' => 'Cargar más',
'image_image_name' => 'Nombre de imagen',
- 'image_delete_confirm' => 'Esta imagen está siendo utilizada en las páginas mostradas a continuación, haga click de nuevo para confirmar que quiere borrar esta imagen.',
+ 'image_delete_used' => 'Esta imagen está siendo utilizada en las páginas mostradas a continuación.',
+ 'image_delete_confirm' => 'Haga click de nuevo para confirmar que quiere borrar esta imagen.',
'image_select_image' => 'Seleccionar Imagen',
'image_dropzone' => 'Arrastre las imágenes o hacer click aquí para Subir',
'images_deleted' => 'Imágenes borradas',
'image_uploaded' => 'Subido el :uploadedDate',
'image_load_more' => 'Cargar más',
'image_image_name' => 'Nombre de imagen',
- 'image_delete_confirm' => 'Esta imagen esta siendo utilizada en las páginas a continuación, haga click de nuevo para confirmar que quiere borrar esta imagen.',
+ 'image_delete_used' => 'Esta imagen esta siendo utilizada en las páginas a continuación.',
+ 'image_delete_confirm' => 'Haga click de nuevo para confirmar que quiere borrar esta imagen.',
'image_select_image' => 'Seleccionar Imagen',
'image_dropzone' => 'Arrastre las imágenes o hacer click aquí para Subir',
'images_deleted' => 'Imágenes borradas',
'image_uploaded' => 'Ajoutée le :uploadedDate',
'image_load_more' => 'Charger plus',
'image_image_name' => 'Nom de l\'image',
- 'image_delete_confirm' => 'Cette image est utilisée dans les pages ci-dessous. Confirmez que vous souhaitez bien supprimer cette image.',
+ 'image_delete_used' => 'Cette image est utilisée dans les pages ci-dessous.',
+ 'image_delete_confirm' => 'Confirmez que vous souhaitez bien supprimer cette image.',
'image_select_image' => 'Selectionner l\'image',
'image_dropzone' => 'Glissez les images ici ou cliquez pour les ajouter',
'images_deleted' => 'Images supprimées',
'image_uploaded' => 'Uploaded :uploadedDate',
'image_load_more' => 'Carica Altre',
'image_image_name' => 'Nome Immagine',
- 'image_delete_confirm' => 'Questa immagine è usata nelle pagine elencate, clicca elimina nuovamente per confermare.',
+ 'image_delete_used' => 'Questa immagine è usata nelle pagine elencate.',
+ 'image_delete_confirm' => 'Clicca elimina nuovamente per confermare.',
'image_select_image' => 'Seleziona Immagine',
'image_dropzone' => 'Rilascia immagini o clicca qui per caricarle',
'images_deleted' => 'Immagini Eliminate',
'image_uploaded' => 'アップロード日時: :uploadedDate',
'image_load_more' => 'さらに読み込む',
'image_image_name' => '画像名',
- 'image_delete_confirm' => 'この画像は以下のページで利用されています。削除してもよろしければ、再度ボタンを押して下さい。',
+ 'image_delete_used' => 'この画像は以下のページで利用されています。',
+ 'image_delete_confirm' => '削除してもよろしければ、再度ボタンを押して下さい。',
'image_select_image' => '選択',
'image_dropzone' => '画像をドロップするか、クリックしてアップロード',
'images_deleted' => '画像を削除しました',
'image_uploaded' => 'Uploaded :uploadedDate',
'image_load_more' => 'Meer Laden',
'image_image_name' => 'Afbeeldingsnaam',
- 'image_delete_confirm' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik, Klik opnieuw op verwijderen om de afbeelding echt te verwijderen.',
+ 'image_delete_used' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik.',
+ 'image_delete_confirm' => 'Klik opnieuw op verwijderen om de afbeelding echt te verwijderen.',
'image_select_image' => 'Kies Afbeelding',
'image_dropzone' => 'Sleep afbeeldingen hier of klik hier om te uploaden',
'images_deleted' => 'Verwijderde Afbeeldingen',
'image_uploaded' => 'Udostępniono :uploadedDate',
'image_load_more' => 'Wczytaj więcej',
'image_image_name' => 'Nazwa obrazka',
- 'image_delete_confirm' => 'Ten obrazek jest używany na stronach poniżej, kliknij ponownie Usuń by potwierdzić usunięcie obrazka.',
+ 'image_delete_used' => 'Ten obrazek jest używany na stronach poniżej.',
+ 'image_delete_confirm' => 'Kliknij ponownie Usuń by potwierdzić usunięcie obrazka.',
'image_select_image' => 'Wybierz obrazek',
'image_dropzone' => 'Upuść obrazki tutaj lub kliknij by wybrać obrazki do udostępnienia',
'images_deleted' => 'Usunięte obrazki',
'image_uploaded' => 'Carregado :uploadedDate',
'image_load_more' => 'Carregar Mais',
'image_image_name' => 'Nome da Imagem',
- 'image_delete_confirm' => 'Essa imagem é usada nas páginas abaixo. Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.',
+ 'image_delete_used' => 'Essa imagem é usada nas páginas abaixo.',
+ 'image_delete_confirm' => 'Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.',
'image_select_image' => 'Selecionar Imagem',
'image_dropzone' => 'Arraste imagens ou clique aqui para fazer upload',
'images_deleted' => 'Imagens excluídas',
'image_uploaded' => 'Загруженно :uploadedDate',
'image_load_more' => 'Загрузить ещё',
'image_image_name' => 'Имя изображения',
- 'image_delete_confirm' => 'Это изображение используется на странице ниже. Снова кликните удалить для подтверждения того что вы хотите удалить.',
+ 'image_delete_used' => 'Это изображение используется на странице ниже.',
+ 'image_delete_confirm' => 'Снова кликните удалить для подтверждения того что вы хотите удалить.',
'image_select_image' => 'Выбрать изображение',
'image_dropzone' => 'Перетащите изображение или кликните для загрузки',
'images_deleted' => 'Изображения удалены',
'image_uploaded' => 'Nahrané :uploadedDate',
'image_load_more' => 'Načítať viac',
'image_image_name' => 'Názov obrázka',
- 'image_delete_confirm' => 'Tento obrázok je použitý na stránkach uvedených nižšie, kliknite znova na zmazať pre potvrdenie zmazania tohto obrázka.',
+ 'image_delete_used' => 'Tento obrázok je použitý na stránkach uvedených nižšie.',
+ 'image_delete_confirm' => 'Kliknite znova na zmazať pre potvrdenie zmazania tohto obrázka.',
'image_select_image' => 'Vybrať obrázok',
'image_dropzone' => 'Presuňte obrázky sem alebo kliknite sem pre nahranie',
'images_deleted' => 'Obrázky zmazané',
'image_uploaded' => 'Laddades upp :uploadedDate',
'image_load_more' => 'Ladda fler',
'image_image_name' => 'Bildnamn',
- 'image_delete_confirm' => 'Den här bilden används på nedanstående sidor, klicka på "ta bort" en gång till för att bekräfta att du vill ta bort bilden.',
+ 'image_delete_used' => 'Den här bilden används på nedanstående sidor.',
+ 'image_delete_confirm' => 'Klicka på "ta bort" en gång till för att bekräfta att du vill ta bort bilden.',
'image_select_image' => 'Välj bild',
'image_dropzone' => 'Släpp bilder här eller klicka för att ladda upp',
'images_deleted' => 'Bilder borttagna',
'image_uploaded' => '上传于 :uploadedDate',
'image_load_more' => '显示更多',
'image_image_name' => '图片名称',
- 'image_delete_confirm' => '该图像用于以下页面,如果你想删除它,请再次按下按钮。',
+ 'image_delete_used' => '该图像用于以下页面。',
+ 'image_delete_confirm' => '如果你想删除它,请再次按下按钮。',
'image_select_image' => '选择图片',
'image_dropzone' => '拖放图片或点击此处上传',
'images_deleted' => '图片已删除',
'image_uploaded' => '上傳於 :uploadedDate',
'image_load_more' => '載入更多',
'image_image_name' => '圖片名稱',
- 'image_delete_confirm' => '所使用圖片目前用於以下頁面,如果你想刪除它,請再次按下按鈕。',
+ 'image_delete_used' => '所使用圖片目前用於以下頁面。',
+ 'image_delete_confirm' => '如果你想刪除它,請再次按下按鈕。',
'image_select_image' => '選擇圖片',
'image_dropzone' => '拖曳圖片或點選這裡上傳',
'images_deleted' => '圖片已刪除',
<div id="image-manager" image-type="{{ $imageType }}" uploaded-to="{{ $uploaded_to or 0 }}">
- <div overlay v-cloak @click="hide()">
+ <div overlay v-cloak @click="hide">
<div class="popup-body" @click.stop="">
<div class="popup-header primary-background">
</div>
<div class="image-manager-sidebar">
+
+ <dropzone ref="dropzone" placeholder="{{ trans('components.image_dropzone') }}" :upload-url="uploadUrl" :uploaded-to="uploadedTo" @success="uploadSuccess"></dropzone>
+
<div class="inner">
<div class="image-manager-details anim fadeIn" v-if="selectedImage">
<form @submit.prevent="saveImageDetails">
<div>
<a :href="selectedImage.url" target="_blank" style="display: block;">
- <img :src="selectedImage.thumbs.gallery" :alt="selectedImage.title"
+ <img :src="selectedImage.thumbs.display" :alt="selectedImage.title"
:title="selectedImage.name">
</a>
</div>
<div class="form-group">
<label for="name">{{ trans('components.image_image_name') }}</label>
- <input id="name" name="name" v-model="selectedImage.name">
+ <input id="name" class="input-base" name="name" v-model="selectedImage.name">
</div>
</form>
- <div v-show="dependantPages">
- <p class="text-neg text-small">
- {{ trans('components.image_delete_confirm') }}
- </p>
- <ul class="text-neg">
- <li v-for="page in dependantPages">
- <a :href="page.url" target="_blank" class="text-neg" v-text="page.name"></a>
- </li>
- </ul>
- </div>
-
<div class="clearfix">
- <form class="float left" @submit.prevent="deleteImage">
- <button class="button icon neg">@icon('delete')</button>
- </form>
- <button class="button pos anim fadeIn float right" v-show="selectedImage" @click="callbackAndHide(selectedImage)">
+ <div class="float left">
+ <button type="button" class="button icon outline" @click="deleteImage">@icon('delete')</button>
+
+ </div>
+ <button class="button anim fadeIn float right" v-show="selectedImage" @click="callbackAndHide(selectedImage)">
{{ trans('components.image_select_image') }}
</button>
+ <div class="clearfix"></div>
+ <div v-show="dependantPages">
+ <p class="text-neg text-small">
+ {{ trans('components.image_delete_used') }}
+ </p>
+ <ul class="text-neg">
+ <li v-for="page in dependantPages">
+ <a :href="page.url" target="_blank" class="text-neg" v-text="page.name"></a>
+ </li>
+ </ul>
+ </div>
+ <div v-show="deleteConfirm" class="text-neg text-small">
+ {{ trans('components.image_delete_confirm') }}
+ </div>
</div>
</div>
- <dropzone ref="dropzone" placeholder="{{ trans('components.image_dropzone') }}" :upload-url="uploadUrl" :uploaded-to="uploadedTo" @success="uploadSuccess"></dropzone>
+
</div>
</div>
Route::put('/update/{imageId}', 'ImageController@update');
Route::post('/drawing/upload', 'ImageController@uploadDrawing');
Route::put('/drawing/upload/{id}', 'ImageController@replaceDrawing');
+ Route::get('/usage/{id}', 'ImageController@usage');
Route::post('/{type}/upload', 'ImageController@uploadByType');
Route::get('/{type}/all', 'ImageController@getAllByType');
Route::get('/{type}/all/{page}', 'ImageController@getAllByType');