From fdb72ff3e91e291d8575c453ad81a9abc1eb31cc Mon Sep 17 00:00:00 2001 From: Biscuit Date: Thu, 16 Oct 2025 14:39:18 +0200 Subject: [PATCH] Add attachment preview insertion --- app/Uploads/Attachment.php | 26 +++++++++++++++ lang/en/entities.php | 1 + resources/js/components/attachments.js | 33 +++++++++++++++---- resources/sass/_content.scss | 13 ++++++++ .../views/attachments/manager-list.blade.php | 10 ++++++ tests/Uploads/AttachmentTest.php | 33 +++++++++++++++++++ 6 files changed, 110 insertions(+), 6 deletions(-) diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index 05227243a..9d6bde60e 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -92,6 +92,32 @@ class Attachment extends Model implements OwnableInterface return ['text/html' => $this->htmlLink(), 'text/plain' => $this->markdownLink()]; } + /** + * Determine if this attachment can provide preview content for editor insertion. + */ + public function hasPreviewContent(): bool + { + if ($this->external) { + return false; + } + + $previewExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx']; + + return in_array(strtolower($this->extension), $previewExtensions); + } + + /** + * Get preview-friendly content for insertion into the editors. + */ + public function editorPreviewContent(): array + { + $html = '
' + . '' + . '
'; + + return ['text/html' => $html, 'text/plain' => $html]; + } + /** * Generate the HTML link to this attachment. */ diff --git a/lang/en/entities.php b/lang/en/entities.php index 74c50be3b..bfd0425bf 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -369,6 +369,7 @@ return [ 'attachments_link_url_hint' => 'Url of site or file', 'attach' => 'Attach', 'attachments_insert_link' => 'Add Attachment Link to Page', + 'attachments_insert_preview' => 'Add Attachment Preview to Page', 'attachments_edit_file' => 'Edit File', 'attachments_edit_file_name' => 'File Name', 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite', diff --git a/resources/js/components/attachments.js b/resources/js/components/attachments.js index 2dc7313a8..1c1bb8094 100644 --- a/resources/js/components/attachments.js +++ b/resources/js/components/attachments.js @@ -33,12 +33,11 @@ export class Attachments extends Component { }); this.container.addEventListener('event-emit-select-insert', event => { - const insertContent = event.target.closest('[data-drag-content]').getAttribute('data-drag-content'); - const contentTypes = JSON.parse(insertContent); - window.$events.emit('editor::insert', { - html: contentTypes['text/html'], - markdown: contentTypes['text/plain'], - }); + this.insertContentFromCard(event, 'data-drag-content'); + }); + + this.container.addEventListener('event-emit-select-insert-preview', event => { + this.insertContentFromCard(event, 'data-preview-content'); }); this.attachLinkButton.addEventListener('click', () => { @@ -66,6 +65,28 @@ export class Attachments extends Component { }); } + insertContentFromCard(event, attributeName) { + const card = event.target.closest(`[${attributeName}]`); + if (!card) { + return; + } + + const insertContent = card.getAttribute(attributeName); + if (!insertContent) { + return; + } + + const contentTypes = JSON.parse(insertContent); + const html = contentTypes['text/html'] ?? ''; + const markdown = contentTypes['text/plain'] ?? html; + + if (!html && !markdown) { + return; + } + + window.$events.emit('editor::insert', {html, markdown}); + } + updateOrder(idOrder) { window.$http.put(`/attachments/sort/page/${this.pageId}`, {order: idOrder}).then(resp => { window.$events.emit('success', resp.data.message); diff --git a/resources/sass/_content.scss b/resources/sass/_content.scss index aba1556a9..844b62c95 100644 --- a/resources/sass/_content.scss +++ b/resources/sass/_content.scss @@ -117,6 +117,19 @@ max-width: 100%; } + .attachment-preview { + margin: vars.$m 0; + } + + .attachment-preview-frame { + width: 100%; + min-height: 360px; + border-radius: 4px; + border: 1px solid; + @include mixins.lightDark(border-color, #DDD, #444); + @include mixins.lightDark(background-color, #FFF, #111); + } + a { text-decoration: underline; } diff --git a/resources/views/attachments/manager-list.blade.php b/resources/views/attachments/manager-list.blade.php index 10ede4aae..9bb711422 100644 --- a/resources/views/attachments/manager-list.blade.php +++ b/resources/views/attachments/manager-list.blade.php @@ -5,6 +5,9 @@ option:ajax-delete-row:url="{{ url('/attachments/' . $attachment->id) }}" data-id="{{ $attachment->id }}" data-drag-content="{{ json_encode($attachment->editorContent()) }}" + @if($attachment->hasPreviewContent()) + data-preview-content="{{ json_encode($attachment->editorPreviewContent()) }}" + @endif class="card drag-card">
@icon('grip')
@@ -16,6 +19,13 @@ type="button" title="{{ trans('entities.attachments_insert_link') }}" class="drag-card-action text-center text-link">@icon('link') + @if($attachment->hasPreviewContent()) + + @endif @if(userCan(\BookStack\Permissions\Permission::AttachmentUpdate, $attachment))