diff --git a/app/Console/Commands/CleanupImages.php b/app/Console/Commands/CleanupImages.php index 310a7bb24..5eadf2751 100644 --- a/app/Console/Commands/CleanupImages.php +++ b/app/Console/Commands/CleanupImages.php @@ -4,6 +4,7 @@ namespace BookStack\Console\Commands; use BookStack\Services\ImageService; use Illuminate\Console\Command; +use Symfony\Component\Console\Output\OutputInterface; class CleanupImages extends Command { @@ -48,21 +49,35 @@ class CleanupImages extends Command $dryRun = $this->option('force') ? false : true; if (!$dryRun) { - $proceed = $this->confirm('This operation is destructive and is not guaranteed to be fully accurate. Ensure you have a backup of your images. Are you sure you want to proceed?'); + $proceed = $this->confirm("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\nAre you sure you want to proceed?"); if (!$proceed) { return; } } - $deleteCount = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleted = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleteCount = count($deleted); if ($dryRun) { $this->comment('Dry run, No images have been deleted'); $this->comment($deleteCount . ' images found that would have been deleted'); + $this->showDeletedImages($deleted); $this->comment('Run with -f or --force to perform deletions'); return; } + $this->showDeletedImages($deleted); $this->comment($deleteCount . ' images deleted'); } + + protected function showDeletedImages($paths) + { + if ($this->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) return; + if (count($paths) > 0) { + $this->line('Images to delete:'); + } + foreach ($paths as $path) { + $this->line($path); + } + } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index ce108e172..d1193ab4f 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -301,38 +301,41 @@ class ImageService extends UploadService /** * Delete gallery and drawings that are not within HTML content of pages or page revisions. + * Checks based off of only the image name. + * Could be much improved to be more specific but kept it generic for now to be safe. + * + * Returns the path of the images that would be/have been deleted. * @param bool $checkRevisions * @param array $types * @param bool $dryRun - * @return int + * @return array */ public function deleteUnusedImages($checkRevisions = true, $types = ['gallery', 'drawio'], $dryRun = true) { - // TODO - The checking here isn't really good enough. - // Thumbnails would also need to be searched for as we can't guarantee the full image will be in the content. - // Would also be best to simplify the string to not include the base host? $types = array_intersect($types, ['gallery', 'drawio']); - $deleteCount = 0; + $deletedPaths = []; + $this->image->newQuery()->whereIn('type', $types) - ->chunk(1000, function($images) use ($types, $checkRevisions, &$deleteCount, $dryRun) { + ->chunk(1000, function($images) use ($types, $checkRevisions, &$deletedPaths, $dryRun) { foreach ($images as $image) { + $searchQuery = '%' . basename($image->path) . '%'; $inPage = DB::table('pages') - ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + ->where('html', 'like', $searchQuery)->count() > 0; $inRevision = false; if ($checkRevisions) { $inRevision = DB::table('page_revisions') - ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + ->where('html', 'like', $searchQuery)->count() > 0; } if (!$inPage && !$inRevision) { - $deleteCount++; + $deletedPaths[] = $image->path; if (!$dryRun) { $this->destroy($image); } } } }); - return $deleteCount; + return $deletedPaths; } /** diff --git a/resources/assets/js/components/markdown-editor.js b/resources/assets/js/components/markdown-editor.js index 46c54408b..06426bf34 100644 --- a/resources/assets/js/components/markdown-editor.js +++ b/resources/assets/js/components/markdown-editor.js @@ -52,6 +52,10 @@ class MarkdownEditor { let action = button.getAttribute('data-action'); if (action === 'insertImage') this.actionInsertImage(); if (action === 'insertLink') this.actionShowLinkSelector(); + if (action === 'insertDrawing' && event.ctrlKey) { + this.actionShowImageManager(); + return; + } if (action === 'insertDrawing') this.actionStartDrawing(); }); @@ -293,7 +297,14 @@ class MarkdownEditor { this.cm.focus(); this.cm.replaceSelection(newText); this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); - }); + }, 'gallery'); + } + + actionShowImageManager() { + let cursorPos = this.cm.getCursor('from'); + window.ImageManager.show(image => { + this.insertDrawing(image, cursorPos); + }, 'drawio'); } // Show the popup link selector and insert a link when finished @@ -324,10 +335,7 @@ class MarkdownEditor { }; window.$http.post(window.baseUrl('/images/drawing/upload'), data).then(resp => { - let newText = `
`; - this.cm.focus(); - this.cm.replaceSelection(newText); - this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); + this.insertDrawing(resp.data, cursorPos); DrawIO.close(); }).catch(err => { window.$events.emit('error', trans('errors.image_upload_error')); @@ -336,6 +344,13 @@ class MarkdownEditor { }); } + insertDrawing(image, originalCursor) { + let newText = `
`; + this.cm.focus(); + this.cm.replaceSelection(newText); + this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length); + } + // Show draw.io if enabled and handle save. actionEditDrawing(imgContainer) { if (document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true') return; @@ -353,8 +368,8 @@ class MarkdownEditor { uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id')) }; - window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => { - let newText = `
`; + window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => { + let newText = `
`; let newContent = this.cm.getValue().split('\n').map(line => { if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) { return newText; diff --git a/resources/assets/js/vues/image-manager.js b/resources/assets/js/vues/image-manager.js index f355107c0..16c8ef9cf 100644 --- a/resources/assets/js/vues/image-manager.js +++ b/resources/assets/js/vues/image-manager.js @@ -101,6 +101,7 @@ const methods = { }, cancelSearch() { + if (!this.searching) return; this.searching = false; this.searchTerm = ''; this.images = preSearchImages;