From d41452f39c90deaca98b4fe0e8c87f7d7aa395b8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 4 Jul 2020 16:53:02 +0100 Subject: [PATCH] Finished breakdown of attachment vue into components --- app/Http/Controllers/AttachmentController.php | 107 +++++++------ package-lock.json | 8 - package.json | 3 +- readme.md | 2 +- resources/js/components/ajax-form.js | 58 +++++++ resources/js/components/attachments.js | 50 ++++-- resources/js/components/dropzone.js | 17 ++- resources/js/components/event-emit-select.js | 29 ++++ resources/js/services/dom.js | 17 +++ resources/js/services/http.js | 17 ++- resources/js/vues/attachment-manager.js | 142 ------------------ resources/js/vues/vues.js | 2 - resources/lang/en/errors.php | 1 - resources/views/attachments/list.blade.php | 8 + .../attachments/manager-edit-form.blade.php | 47 ++++++ .../attachments/manager-link-form.blade.php | 27 ++++ .../manager-list.blade.php} | 6 +- resources/views/attachments/manager.blade.php | 39 +++++ resources/views/common/home.blade.php | 2 - resources/views/components/dropzone.blade.php | 6 + .../views/pages/attachment-manager.blade.php | 92 ------------ .../views/pages/editor-toolbox.blade.php | 2 +- resources/views/pages/show.blade.php | 9 +- routes/web.php | 1 + 24 files changed, 371 insertions(+), 321 deletions(-) create mode 100644 resources/js/components/ajax-form.js create mode 100644 resources/js/components/event-emit-select.js delete mode 100644 resources/js/vues/attachment-manager.js create mode 100644 resources/views/attachments/list.blade.php create mode 100644 resources/views/attachments/manager-edit-form.blade.php create mode 100644 resources/views/attachments/manager-link-form.blade.php rename resources/views/{pages/attachment-list.blade.php => attachments/manager-list.blade.php} (80%) create mode 100644 resources/views/attachments/manager.blade.php delete mode 100644 resources/views/pages/attachment-manager.blade.php diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index f209d6a94..0830693bc 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -8,6 +8,7 @@ use BookStack\Uploads\AttachmentService; use Exception; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Http\Request; +use Illuminate\Support\MessageBag; use Illuminate\Validation\ValidationException; class AttachmentController extends Controller @@ -60,25 +61,17 @@ class AttachmentController extends Controller /** * Update an uploaded attachment. * @throws ValidationException - * @throws NotFoundException */ public function uploadUpdate(Request $request, $attachmentId) { $this->validate($request, [ - 'uploaded_to' => 'required|integer|exists:pages,id', 'file' => 'required|file' ]); - $pageId = $request->get('uploaded_to'); - $page = $this->pageRepo->getById($pageId); - $attachment = $this->attachment->findOrFail($attachmentId); - - $this->checkOwnablePermission('page-update', $page); + $attachment = $this->attachment->newQuery()->findOrFail($attachmentId); + $this->checkOwnablePermission('view', $attachment->page); + $this->checkOwnablePermission('page-update', $attachment->page); $this->checkOwnablePermission('attachment-create', $attachment); - - if (intval($pageId) !== intval($attachment->uploaded_to)) { - return $this->jsonError(trans('errors.attachment_page_mismatch')); - } $uploadedFile = $request->file('file'); @@ -92,57 +85,87 @@ class AttachmentController extends Controller } /** - * Update the details of an existing file. - * @throws ValidationException - * @throws NotFoundException + * Get the update form for an attachment. + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function update(Request $request, $attachmentId) + public function getUpdateForm(string $attachmentId) { - $this->validate($request, [ - 'uploaded_to' => 'required|integer|exists:pages,id', - 'name' => 'required|string|min:1|max:255', - 'link' => 'string|min:1|max:255' - ]); - - $pageId = $request->get('uploaded_to'); - $page = $this->pageRepo->getById($pageId); $attachment = $this->attachment->findOrFail($attachmentId); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission('page-update', $attachment->page); $this->checkOwnablePermission('attachment-create', $attachment); - if (intval($pageId) !== intval($attachment->uploaded_to)) { - return $this->jsonError(trans('errors.attachment_page_mismatch')); + return view('attachments.manager-edit-form', [ + 'attachment' => $attachment, + ]); + } + + /** + * Update the details of an existing file. + */ + public function update(Request $request, string $attachmentId) + { + $attachment = $this->attachment->newQuery()->findOrFail($attachmentId); + + try { + $this->validate($request, [ + 'attachment_edit_name' => 'required|string|min:1|max:255', + 'attachment_edit_url' => 'string|min:1|max:255' + ]); + } catch (ValidationException $exception) { + return response()->view('attachments.manager-edit-form', array_merge($request->only(['attachment_edit_name', 'attachment_edit_url']), [ + 'attachment' => $attachment, + 'errors' => new MessageBag($exception->errors()), + ]), 422); } - $attachment = $this->attachmentService->updateFile($attachment, $request->all()); - return response()->json($attachment); + $this->checkOwnablePermission('view', $attachment->page); + $this->checkOwnablePermission('page-update', $attachment->page); + $this->checkOwnablePermission('attachment-create', $attachment); + + $attachment = $this->attachmentService->updateFile($attachment, [ + 'name' => $request->get('attachment_edit_name'), + 'link' => $request->get('attachment_edit_url'), + ]); + + return view('attachments.manager-edit-form', [ + 'attachment' => $attachment, + ]); } /** * Attach a link to a page. - * @throws ValidationException * @throws NotFoundException */ public function attachLink(Request $request) { - $this->validate($request, [ - 'uploaded_to' => 'required|integer|exists:pages,id', - 'name' => 'required|string|min:1|max:255', - 'link' => 'required|string|min:1|max:255' - ]); + $pageId = $request->get('attachment_link_uploaded_to'); + + try { + $this->validate($request, [ + 'attachment_link_uploaded_to' => 'required|integer|exists:pages,id', + 'attachment_link_name' => 'required|string|min:1|max:255', + 'attachment_link_url' => 'required|string|min:1|max:255' + ]); + } catch (ValidationException $exception) { + return response()->view('attachments.manager-link-form', array_merge($request->only(['attachment_link_name', 'attachment_link_url']), [ + 'pageId' => $pageId, + 'errors' => new MessageBag($exception->errors()), + ]), 422); + } - $pageId = $request->get('uploaded_to'); $page = $this->pageRepo->getById($pageId); $this->checkPermission('attachment-create-all'); $this->checkOwnablePermission('page-update', $page); - $attachmentName = $request->get('name'); - $link = $request->get('link'); + $attachmentName = $request->get('attachment_link_name'); + $link = $request->get('attachment_link_url'); $attachment = $this->attachmentService->saveNewFromLink($attachmentName, $link, $pageId); - return response()->json($attachment); + return view('attachments.manager-link-form', [ + 'pageId' => $pageId, + ]); } /** @@ -152,7 +175,7 @@ class AttachmentController extends Controller { $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-view', $page); - return view('pages.attachment-list', [ + return view('attachments.manager-list', [ 'attachments' => $page->attachments->all(), ]); } @@ -180,7 +203,7 @@ class AttachmentController extends Controller * @throws FileNotFoundException * @throws NotFoundException */ - public function get(int $attachmentId) + public function get(string $attachmentId) { $attachment = $this->attachment->findOrFail($attachmentId); try { @@ -201,11 +224,9 @@ class AttachmentController extends Controller /** * Delete a specific attachment in the system. - * @param $attachmentId - * @return mixed * @throws Exception */ - public function delete(int $attachmentId) + public function delete(string $attachmentId) { $attachment = $this->attachment->findOrFail($attachmentId); $this->checkOwnablePermission('attachment-delete', $attachment); diff --git a/package-lock.json b/package-lock.json index b60173683..3687566ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3801,14 +3801,6 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" }, - "vuedraggable": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.23.2.tgz", - "integrity": "sha512-PgHCjUpxEAEZJq36ys49HfQmXglattf/7ofOzUrW2/rRdG7tu6fK84ir14t1jYv4kdXewTEa2ieKEAhhEMdwkQ==", - "requires": { - "sortablejs": "^1.10.1" - } - }, "watchpack": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", diff --git a/package.json b/package.json index 7462dab92..0f1855cd9 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,7 @@ "markdown-it": "^11.0.0", "markdown-it-task-lists": "^2.1.1", "sortablejs": "^1.10.2", - "vue": "^2.6.11", - "vuedraggable": "^2.23.2" + "vue": "^2.6.11" }, "browser": { "vue": "vue/dist/vue.common.js" diff --git a/readme.md b/readme.md index 2c68d094c..c6db7a8e4 100644 --- a/readme.md +++ b/readme.md @@ -158,7 +158,7 @@ These are the great open-source projects used to help build BookStack: * [TinyMCE](https://www.tinymce.com/) * [CodeMirror](https://codemirror.net) * [Vue.js](http://vuejs.org/) -* [Sortable](https://github.com/SortableJS/Sortable) & [Vue.Draggable](https://github.com/SortableJS/Vue.Draggable) +* [Sortable](https://github.com/SortableJS/Sortable) * [Google Material Icons](https://material.io/icons/) * [Dropzone.js](http://www.dropzonejs.com/) * [clipboard.js](https://clipboardjs.com/) diff --git a/resources/js/components/ajax-form.js b/resources/js/components/ajax-form.js new file mode 100644 index 000000000..92b19dcff --- /dev/null +++ b/resources/js/components/ajax-form.js @@ -0,0 +1,58 @@ +import {onEnterPress, onSelect} from "../services/dom"; + +/** + * Ajax Form + * Will handle button clicks or input enter press events and submit + * the data over ajax. Will always expect a partial HTML view to be returned. + * Fires an 'ajax-form-success' event when submitted successfully. + * @extends {Component} + */ +class AjaxForm { + setup() { + this.container = this.$el; + this.url = this.$opts.url; + this.method = this.$opts.method || 'post'; + this.successMessage = this.$opts.successMessage; + this.submitButtons = this.$manyRefs.submit || []; + + this.setupListeners(); + } + + setupListeners() { + onEnterPress(this.container, event => { + this.submit(); + event.preventDefault(); + }); + + this.submitButtons.forEach(button => onSelect(button, this.submit.bind(this))); + } + + async submit() { + const fd = new FormData(); + const inputs = this.container.querySelectorAll(`[name]`); + console.log(inputs); + for (const input of inputs) { + fd.append(input.getAttribute('name'), input.value); + } + + this.container.style.opacity = '0.7'; + this.container.style.pointerEvents = 'none'; + try { + const resp = await window.$http[this.method.toLowerCase()](this.url, fd); + this.container.innerHTML = resp.data; + this.$emit('success', {formData: fd}); + if (this.successMessage) { + window.$events.emit('success', this.successMessage); + } + } catch (err) { + this.container.innerHTML = err.data; + } + + window.components.init(this.container); + this.container.style.opacity = null; + this.container.style.pointerEvents = null; + } + +} + +export default AjaxForm; \ No newline at end of file diff --git a/resources/js/components/attachments.js b/resources/js/components/attachments.js index 49ba8f388..51e54054e 100644 --- a/resources/js/components/attachments.js +++ b/resources/js/components/attachments.js @@ -1,14 +1,16 @@ - /** * Attachments * @extends {Component} */ +import {showLoading} from "../services/dom"; + class Attachments { setup() { this.container = this.$el; this.pageId = this.$opts.pageId; this.editContainer = this.$refs.editContainer; + this.listContainer = this.$refs.listContainer; this.mainTabs = this.$refs.mainTabs; this.list = this.$refs.list; @@ -16,23 +18,30 @@ class Attachments { } setupListeners() { - this.container.addEventListener('dropzone-success', event => { - this.mainTabs.components.tabs.show('items'); - window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => { - this.list.innerHTML = resp.data; - window.components.init(this.list); - }) - }); + const reloadListBound = this.reloadList.bind(this); + this.container.addEventListener('dropzone-success', reloadListBound); + this.container.addEventListener('ajax-form-success', reloadListBound); this.container.addEventListener('sortable-list-sort', event => { this.updateOrder(event.detail.ids); }); - this.editContainer.addEventListener('keypress', event => { - if (event.key === 'Enter') { - // TODO - Update editing file - } - }) + this.container.addEventListener('event-emit-select-edit', event => { + this.startEdit(event.detail.id); + }); + + this.container.addEventListener('event-emit-select-edit-back', event => { + this.stopEdit(); + }); + } + + reloadList() { + this.stopEdit(); + this.mainTabs.components.tabs.show('items'); + window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => { + this.list.innerHTML = resp.data; + window.components.init(this.list); + }); } updateOrder(idOrder) { @@ -41,6 +50,21 @@ class Attachments { }); } + async startEdit(id) { + this.editContainer.classList.remove('hidden'); + this.listContainer.classList.add('hidden'); + + showLoading(this.editContainer); + const resp = await window.$http.get(`/attachments/edit/${id}`); + this.editContainer.innerHTML = resp.data; + window.components.init(this.editContainer); + } + + stopEdit() { + this.editContainer.classList.add('hidden'); + this.listContainer.classList.remove('hidden'); + } + } export default Attachments; \ No newline at end of file diff --git a/resources/js/components/dropzone.js b/resources/js/components/dropzone.js index 4b12867aa..5a7e29de5 100644 --- a/resources/js/components/dropzone.js +++ b/resources/js/components/dropzone.js @@ -9,11 +9,15 @@ class Dropzone { setup() { this.container = this.$el; this.url = this.$opts.url; + this.successMessage = this.$opts.successMessage; + this.removeMessage = this.$opts.removeMessage; + this.uploadLimitMessage = this.$opts.uploadLimitMessage; + this.timeoutMessage = this.$opts.timeoutMessage; const _this = this; this.dz = new DropZoneLib(this.container, { addRemoveLinks: true, - dictRemoveFile: window.trans('components.image_upload_remove'), + dictRemoveFile: this.removeMessage, timeout: Number(window.uploadTimeout) || 60000, maxFilesize: Number(window.uploadLimit) || 256, url: this.url, @@ -32,15 +36,20 @@ class Dropzone { const token = window.document.querySelector('meta[name=token]').getAttribute('content'); data.append('_token', token); - xhr.ontimeout = function (e) { + xhr.ontimeout = (e) => { this.dz.emit('complete', file); - this.dz.emit('error', file, window.trans('errors.file_upload_timeout')); + this.dz.emit('error', file, this.timeoutMessage); } } onSuccess(file, data) { this.container.dispatchEvent(new Event('dropzone')) this.$emit('success', {file, data}); + + if (this.successMessage) { + window.$events.emit('success', this.successMessage); + } + fadeOut(file.previewElement, 800, () => { this.dz.removeFile(file); }); @@ -55,7 +64,7 @@ class Dropzone { } if (xhr && xhr.status === 413) { - setMessage(window.trans('errors.server_upload_limit')) + setMessage(this.uploadLimitMessage); } else if (errorMessage.file) { setMessage(errorMessage.file); } diff --git a/resources/js/components/event-emit-select.js b/resources/js/components/event-emit-select.js new file mode 100644 index 000000000..cf0215850 --- /dev/null +++ b/resources/js/components/event-emit-select.js @@ -0,0 +1,29 @@ +import {onSelect} from "../services/dom"; + +/** + * EventEmitSelect + * Component will simply emit an event when selected. + * + * Has one required option: "name". + * A name of "hello" will emit a component DOM event of + * "event-emit-select-name" + * + * All options will be set as the "detail" of the event with + * their values included. + * + * @extends {Component} + */ +class EventEmitSelect { + setup() { + this.container = this.$el; + this.name = this.$opts.name; + + + onSelect(this.$el, () => { + this.$emit(this.name, this.$opts); + }); + } + +} + +export default EventEmitSelect; \ No newline at end of file diff --git a/resources/js/services/dom.js b/resources/js/services/dom.js index 2a9fad8b3..00b34bf34 100644 --- a/resources/js/services/dom.js +++ b/resources/js/services/dom.js @@ -53,6 +53,14 @@ export function onEnterPress(elements, callback) { if (!Array.isArray(elements)) { elements = [elements]; } + + const listener = event => { + if (event.key === 'Enter') { + callback(event); + } + } + + elements.forEach(e => e.addEventListener('keypress', listener)); } /** @@ -89,4 +97,13 @@ export function findText(selector, text) { } } return null; +} + +/** + * Show a loading indicator in the given element. + * This will effectively clear the element. + * @param {Element} element + */ +export function showLoading(element) { + element.innerHTML = `
`; } \ No newline at end of file diff --git a/resources/js/services/http.js b/resources/js/services/http.js index 06dac9864..5b5e1c496 100644 --- a/resources/js/services/http.js +++ b/resources/js/services/http.js @@ -67,11 +67,20 @@ async function dataRequest(method, url, data = null) { body: data, }; + // Send data as JSON if a plain object if (typeof data === 'object' && !(data instanceof FormData)) { options.headers = {'Content-Type': 'application/json'}; options.body = JSON.stringify(data); } + // Ensure FormData instances are sent over POST + // Since Laravel does not read multipart/form-data from other types + // of request. Hence the addition of the magic _method value. + if (data instanceof FormData && method !== 'post') { + data.append('_method', method); + options.method = 'post'; + } + return request(url, options) } @@ -109,7 +118,7 @@ async function request(url, options = {}) { const response = await fetch(url, options); const content = await getResponseContent(response); - return { + const returnData = { data: content, headers: response.headers, redirected: response.redirected, @@ -117,7 +126,13 @@ async function request(url, options = {}) { statusText: response.statusText, url: response.url, original: response, + }; + + if (!response.ok) { + throw returnData; } + + return returnData; } /** diff --git a/resources/js/vues/attachment-manager.js b/resources/js/vues/attachment-manager.js deleted file mode 100644 index 2467c646d..000000000 --- a/resources/js/vues/attachment-manager.js +++ /dev/null @@ -1,142 +0,0 @@ -import draggable from "vuedraggable"; -import dropzone from "./components/dropzone"; - -function mounted() { - this.pageId = this.$el.getAttribute('page-id'); - this.file = this.newFile(); - - this.$http.get(window.baseUrl(`/attachments/get/page/${this.pageId}`)).then(resp => { - this.files = resp.data; - }).catch(err => { - this.checkValidationErrors('get', err); - }); -} - -let data = { - pageId: null, - files: [], - fileToEdit: null, - file: {}, - tab: 'list', - editTab: 'file', - errors: {link: {}, edit: {}, delete: {}} -}; - -const components = {dropzone, draggable}; - -let methods = { - - newFile() { - return {page_id: this.pageId}; - }, - - getFileUrl(file) { - if (file.external && file.path.indexOf('http') !== 0) { - return file.path; - } - return window.baseUrl(`/attachments/${file.id}`); - }, - - fileSortUpdate() { - this.$http.put(window.baseUrl(`/attachments/sort/page/${this.pageId}`), {files: this.files}).then(resp => { - this.$events.emit('success', resp.data.message); - }).catch(err => { - this.checkValidationErrors('sort', err); - }); - }, - - startEdit(file) { - this.fileToEdit = Object.assign({}, file); - this.fileToEdit.link = file.external ? file.path : ''; - this.editTab = file.external ? 'link' : 'file'; - }, - - deleteFile(file) { - if (!file.deleting) { - return this.$set(file, 'deleting', true); - } - - this.$http.delete(window.baseUrl(`/attachments/${file.id}`)).then(resp => { - this.$events.emit('success', resp.data.message); - this.files.splice(this.files.indexOf(file), 1); - }).catch(err => { - this.checkValidationErrors('delete', err) - }); - }, - - uploadSuccess(upload) { - this.files.push(upload.data); - this.$events.emit('success', trans('entities.attachments_file_uploaded')); - }, - - uploadSuccessUpdate(upload) { - let fileIndex = this.filesIndex(upload.data); - if (fileIndex === -1) { - this.files.push(upload.data) - } else { - this.files.splice(fileIndex, 1, upload.data); - } - - if (this.fileToEdit && this.fileToEdit.id === upload.data.id) { - this.fileToEdit = Object.assign({}, upload.data); - } - this.$events.emit('success', trans('entities.attachments_file_updated')); - }, - - checkValidationErrors(groupName, err) { - if (typeof err.response.data === "undefined" && typeof err.response.data === "undefined") return; - this.errors[groupName] = err.response.data; - }, - - getUploadUrl(file) { - let url = window.baseUrl(`/attachments/upload`); - if (typeof file !== 'undefined') url += `/${file.id}`; - return url; - }, - - cancelEdit() { - this.fileToEdit = null; - }, - - attachNewLink(file) { - file.uploaded_to = this.pageId; - this.errors.link = {}; - this.$http.post(window.baseUrl('/attachments/link'), file).then(resp => { - this.files.push(resp.data); - this.file = this.newFile(); - this.$events.emit('success', trans('entities.attachments_link_attached')); - }).catch(err => { - this.checkValidationErrors('link', err); - }); - }, - - updateFile(file) { - $http.put(window.baseUrl(`/attachments/${file.id}`), file).then(resp => { - let search = this.filesIndex(resp.data); - if (search === -1) { - this.files.push(resp.data); - } else { - this.files.splice(search, 1, resp.data); - } - - if (this.fileToEdit && !file.external) this.fileToEdit.link = ''; - this.fileToEdit = false; - - this.$events.emit('success', trans('entities.attachments_updated_success')); - }).catch(err => { - this.checkValidationErrors('edit', err); - }); - }, - - filesIndex(file) { - for (let i = 0, len = this.files.length; i < len; i++) { - if (this.files[i].id === file.id) return i; - } - return -1; - } - -}; - -export default { - data, methods, mounted, components, -}; \ No newline at end of file diff --git a/resources/js/vues/vues.js b/resources/js/vues/vues.js index 0d3817f0e..d0bd529ef 100644 --- a/resources/js/vues/vues.js +++ b/resources/js/vues/vues.js @@ -5,12 +5,10 @@ function exists(id) { } import imageManager from "./image-manager"; -import attachmentManager from "./attachment-manager"; import pageEditor from "./page-editor"; let vueMapping = { 'image-manager': imageManager, - 'attachment-manager': attachmentManager, 'page-editor': pageEditor, }; diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index 06a5285f5..79024e482 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -46,7 +46,6 @@ return [ 'file_upload_timeout' => 'The file upload has timed out.', // Attachments - 'attachment_page_mismatch' => 'Page mismatch during attachment update', 'attachment_not_found' => 'Attachment not found', // Pages diff --git a/resources/views/attachments/list.blade.php b/resources/views/attachments/list.blade.php new file mode 100644 index 000000000..8c9be8290 --- /dev/null +++ b/resources/views/attachments/list.blade.php @@ -0,0 +1,8 @@ +@foreach($attachments as $attachment) +
+ external) target="_blank" @endif> + @icon($attachment->external ? 'export' : 'file') + {{ $attachment->name }} + +
+@endforeach \ No newline at end of file diff --git a/resources/views/attachments/manager-edit-form.blade.php b/resources/views/attachments/manager-edit-form.blade.php new file mode 100644 index 000000000..f3f11a0fc --- /dev/null +++ b/resources/views/attachments/manager-edit-form.blade.php @@ -0,0 +1,47 @@ +
+
{{ trans('entities.attachments_edit_file') }}
+ +
+ + + @if($errors->has('attachment_edit_name')) +
{{ $errors->first('attachment_edit_name') }}
+ @endif +
+ +
+ +
+ @include('components.dropzone', [ + 'placeholder' => trans('entities.attachments_edit_drop_upload'), + 'url' => url('/attachments/upload/' . $attachment->id), + 'successMessage' => trans('entities.attachments_file_updated'), + ]) +
+
+
+ + + @if($errors->has('attachment_edit_url')) +
{{ $errors->first('attachment_edit_url') }}
+ @endif +
+
+
+ + + +
\ No newline at end of file diff --git a/resources/views/attachments/manager-link-form.blade.php b/resources/views/attachments/manager-link-form.blade.php new file mode 100644 index 000000000..6f22abb32 --- /dev/null +++ b/resources/views/attachments/manager-link-form.blade.php @@ -0,0 +1,27 @@ +{{-- +@pageId +--}} +
+ +

{{ trans('entities.attachments_explain_link') }}

+
+ + + @if($errors->has('attachment_link_name')) +
{{ $errors->first('attachment_link_name') }}
+ @endif +
+
+ + + @if($errors->has('attachment_link_url')) +
{{ $errors->first('attachment_link_url') }}
+ @endif +
+ +
\ No newline at end of file diff --git a/resources/views/pages/attachment-list.blade.php b/resources/views/attachments/manager-list.blade.php similarity index 80% rename from resources/views/pages/attachment-list.blade.php rename to resources/views/attachments/manager-list.blade.php index a9801870c..06ab5f912 100644 --- a/resources/views/pages/attachment-list.blade.php +++ b/resources/views/attachments/manager-list.blade.php @@ -9,7 +9,11 @@ {{ $attachment->name }}
- +
- @include('pages.attachment-manager', ['page' => \BookStack\Entities\Page::first()]) - @stop diff --git a/resources/views/components/dropzone.blade.php b/resources/views/components/dropzone.blade.php index 22bf8aff4..6c5ac4929 100644 --- a/resources/views/components/dropzone.blade.php +++ b/resources/views/components/dropzone.blade.php @@ -1,9 +1,15 @@ {{-- @url - URL to upload to. @placeholder - Placeholder text +@successMessage --}}
\ No newline at end of file diff --git a/resources/views/pages/attachment-manager.blade.php b/resources/views/pages/attachment-manager.blade.php deleted file mode 100644 index a86cd70da..000000000 --- a/resources/views/pages/attachment-manager.blade.php +++ /dev/null @@ -1,92 +0,0 @@ -
- - @exposeTranslations([ - 'entities.attachments_file_uploaded', - 'entities.attachments_file_updated', - 'entities.attachments_link_attached', - 'entities.attachments_updated_success', - 'errors.server_upload_limit', - 'components.image_upload_remove', - 'components.file_upload_timeout', - ]) - -

{{ trans('entities.attachments') }}

-
- -
-

{{ trans('entities.attachments_explain') }} {{ trans('entities.attachments_explain_instant_save') }}

- -
- -
- @include('pages.attachment-list', ['attachments' => $page->attachments->all()]) -
-
- @include('components.dropzone', [ - 'placeholder' => trans('entities.attachments_dropzone'), - 'url' => url('/attachments/upload?uploaded_to=' . $page->id) - ]) -
- -
- -
- - - -
-
\ No newline at end of file diff --git a/resources/views/pages/editor-toolbox.blade.php b/resources/views/pages/editor-toolbox.blade.php index 3741c9246..87a9cc2de 100644 --- a/resources/views/pages/editor-toolbox.blade.php +++ b/resources/views/pages/editor-toolbox.blade.php @@ -17,7 +17,7 @@
@if(userCan('attachment-create-all')) - @include('pages.attachment-manager', ['page' => $page]) + @include('attachments.manager', ['page' => $page]) @endif
diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index cfa5ee9ce..48c88434e 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -37,14 +37,7 @@
{{ trans('entities.pages_attachments') }}
- @foreach($page->attachments as $attachment) - - @endforeach + @include('attachments.list', ['attachments' => $page->attachments])
@endif diff --git a/routes/web.php b/routes/web.php index 6b7911825..314515fe8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -124,6 +124,7 @@ Route::group(['middleware' => 'auth'], function () { Route::post('/attachments/upload/{id}', 'AttachmentController@uploadUpdate'); Route::post('/attachments/link', 'AttachmentController@attachLink'); Route::put('/attachments/{id}', 'AttachmentController@update'); + Route::get('/attachments/edit/{id}', 'AttachmentController@getUpdateForm'); Route::get('/attachments/get/page/{pageId}', 'AttachmentController@listForPage'); Route::put('/attachments/sort/page/{pageId}', 'AttachmentController@sortForPage'); Route::delete('/attachments/{id}', 'AttachmentController@delete');