use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Request;
+use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationException;
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');
}
/**
- * 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,
+ ]);
}
/**
{
$page = $this->pageRepo->getById($pageId);
$this->checkOwnablePermission('page-view', $page);
- return view('pages.attachment-list', [
+ return view('attachments.manager-list', [
'attachments' => $page->attachments->all(),
]);
}
* @throws FileNotFoundException
* @throws NotFoundException
*/
- public function get(int $attachmentId)
+ public function get(string $attachmentId)
{
$attachment = $this->attachment->findOrFail($attachmentId);
try {
/**
* 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);
"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",
"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"
* [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/)
--- /dev/null
+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
-
/**
* 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;
}
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) {
});
}
+ 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
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,
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);
});
}
if (xhr && xhr.status === 413) {
- setMessage(window.trans('errors.server_upload_limit'))
+ setMessage(this.uploadLimitMessage);
} else if (errorMessage.file) {
setMessage(errorMessage.file);
}
--- /dev/null
+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
if (!Array.isArray(elements)) {
elements = [elements];
}
+
+ const listener = event => {
+ if (event.key === 'Enter') {
+ callback(event);
+ }
+ }
+
+ elements.forEach(e => e.addEventListener('keypress', listener));
}
/**
}
}
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 = `<div class="loading-container"><div></div><div></div><div></div></div>`;
}
\ No newline at end of file
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)
}
const response = await fetch(url, options);
const content = await getResponseContent(response);
- return {
+ const returnData = {
data: content,
headers: response.headers,
redirected: response.redirected,
statusText: response.statusText,
url: response.url,
original: response,
+ };
+
+ if (!response.ok) {
+ throw returnData;
}
+
+ return returnData;
}
/**
+++ /dev/null
-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
}
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,
};
'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
--- /dev/null
+@foreach($attachments as $attachment)
+ <div class="attachment icon-list">
+ <a class="icon-list-item py-xs" href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
+ <span class="icon">@icon($attachment->external ? 'export' : 'file')</span>
+ <span>{{ $attachment->name }}</span>
+ </a>
+ </div>
+@endforeach
\ No newline at end of file
--- /dev/null
+<div component="ajax-form"
+ option:ajax-form:url="/attachments/{{ $attachment->id }}"
+ option:ajax-form:method="put"
+ option:ajax-form:success-message="{{ trans('entities.attachments_updated_success') }}">
+ <h5>{{ trans('entities.attachments_edit_file') }}</h5>
+
+ <div class="form-group">
+ <label for="attachment_edit_name">{{ trans('entities.attachments_edit_file_name') }}</label>
+ <input type="text" id="attachment_edit_name"
+ name="attachment_edit_name"
+ value="{{ $attachment_edit_name ?? $attachment->name ?? '' }}"
+ placeholder="{{ trans('entities.attachments_edit_file_name') }}">
+ @if($errors->has('attachment_edit_name'))
+ <div class="text-neg text-small">{{ $errors->first('attachment_edit_name') }}</div>
+ @endif
+ </div>
+
+ <div component="tabs" class="tab-container">
+ <div class="nav-tabs">
+ <button refs="tabs@toggleFile" type="button" class="tab-item {{ $attachment->external ? '' : 'selected' }}">{{ trans('entities.attachments_upload') }}</button>
+ <button refs="tabs@toggleLink" type="button" class="tab-item {{ $attachment->external ? 'selected' : '' }}">{{ trans('entities.attachments_set_link') }}</button>
+ </div>
+ <div refs="tabs@contentFile" class="mb-m {{ $attachment->external ? 'hidden' : '' }}">
+ @include('components.dropzone', [
+ 'placeholder' => trans('entities.attachments_edit_drop_upload'),
+ 'url' => url('/attachments/upload/' . $attachment->id),
+ 'successMessage' => trans('entities.attachments_file_updated'),
+ ])
+ </div>
+ <div refs="tabs@contentLink" class="{{ $attachment->external ? '' : 'hidden' }}">
+ <div class="form-group">
+ <label for="attachment_edit_url">{{ trans('entities.attachments_link_url') }}</label>
+ <input type="text" id="attachment_edit_url"
+ name="attachment_edit_url"
+ value="{{ $attachment_edit_url ?? ($attachment->external ? $attachment->path : '') }}"
+ placeholder="{{ trans('entities.attachment_link') }}">
+ @if($errors->has('attachment_edit_url'))
+ <div class="text-neg text-small">{{ $errors->first('attachment_edit_url') }}</div>
+ @endif
+ </div>
+ </div>
+ </div>
+
+ <button component="event-emit-select"
+ option:event-emit-select:name="edit-back" type="button" class="button outline">{{ trans('common.back') }}</button>
+ <button refs="ajax-form@submit" type="button" class="button">{{ trans('common.save') }}</button>
+</div>
\ No newline at end of file
--- /dev/null
+{{--
+@pageId
+--}}
+<div component="ajax-form"
+ option:ajax-form:url="/attachments/link"
+ option:ajax-form:method="post"
+ option:ajax-form:success-message="{{ trans('entities.attachments_link_attached') }}">
+ <input type="hidden" name="attachment_link_uploaded_to" value="{{ $pageId }}">
+ <p class="text-muted small">{{ trans('entities.attachments_explain_link') }}</p>
+ <div class="form-group">
+ <label for="attachment_link_name">{{ trans('entities.attachments_link_name') }}</label>
+ <input name="attachment_link_name" id="attachment_link_name" type="text" placeholder="{{ trans('entities.attachments_link_name') }}" value="{{ $attachment_link_name ?? '' }}">
+ @if($errors->has('attachment_link_name'))
+ <div class="text-neg text-small">{{ $errors->first('attachment_link_name') }}</div>
+ @endif
+ </div>
+ <div class="form-group">
+ <label for="attachment_link_url">{{ trans('entities.attachments_link_url') }}</label>
+ <input name="attachment_link_url" id="attachment_link_url" type="text" placeholder="{{ trans('entities.attachments_link_url_hint') }}" value="{{ $attachment_link_url ?? '' }}">
+ @if($errors->has('attachment_link_url'))
+ <div class="text-neg text-small">{{ $errors->first('attachment_link_url') }}</div>
+ @endif
+ </div>
+ <button refs="ajax-form@submit"
+ type="button"
+ class="button">{{ trans('entities.attach') }}</button>
+</div>
\ No newline at end of file
<a href="{{ $attachment->getUrl() }}" target="_blank">{{ $attachment->name }}</a>
</div>
<div class="flex-fill justify-flex-end">
- <button type="button" class="drag-card-action text-center text-primary">@icon('edit')</button>
+ <button component="event-emit-select"
+ option:event-emit-select:name="edit"
+ option:event-emit-select:id="{{ $attachment->id }}"
+ type="button"
+ class="drag-card-action text-center text-primary">@icon('edit')</button>
<div component="dropdown" class="flex-fill relative">
<button refs="dropdown@toggle" type="button" class="drag-card-action text-center text-neg">@icon('close')</button>
<div refs="dropdown@menu" class="dropdown-menu">
--- /dev/null
+<div style="display: block;" toolbox-tab-content="files"
+ component="attachments"
+ option:attachments:page-id="{{ $page->id ?? 0 }}">
+
+ <h4>{{ trans('entities.attachments') }}</h4>
+ <div class="px-l files">
+
+ <div refs="attachments@listContainer">
+ <p class="text-muted small">{{ trans('entities.attachments_explain') }} <span class="text-warn">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
+
+ <div component="tabs" refs="attachments@mainTabs" class="tab-container">
+ <div class="nav-tabs">
+ <button refs="tabs@toggleItems" type="button" class="selected tab-item">{{ trans('entities.attachments_items') }}</button>
+ <button refs="tabs@toggleUpload" type="button" class="tab-item">{{ trans('entities.attachments_upload') }}</button>
+ <button refs="tabs@toggleLinks" type="button" class="tab-item">{{ trans('entities.attachments_link') }}</button>
+ </div>
+ <div refs="tabs@contentItems attachments@list">
+ @include('attachments.manager-list', ['attachments' => $page->attachments->all()])
+ </div>
+ <div refs="tabs@contentUpload" class="hidden">
+ @include('components.dropzone', [
+ 'placeholder' => trans('entities.attachments_dropzone'),
+ 'url' => url('/attachments/upload?uploaded_to=' . $page->id),
+ 'successMessage' => trans('entities.attachments_file_uploaded'),
+ ])
+ </div>
+ <div refs="tabs@contentLinks" class="hidden">
+ @include('attachments.manager-link-form', ['pageId' => $page->id])
+ </div>
+ </div>
+
+ </div>
+
+ <div refs="attachments@editContainer" class="hidden">
+
+ </div>
+
+ </div>
+</div>
\ No newline at end of file
</div>
</div>
- @include('pages.attachment-manager', ['page' => \BookStack\Entities\Page::first()])
-
@stop
{{--
@url - URL to upload to.
@placeholder - Placeholder text
+@successMessage
--}}
<div component="dropzone"
option:dropzone:url="{{ $url }}"
+ option:dropzone:success-message="{{ $successMessage ?? '' }}"
+ option:dropzone:remove-message="{{ trans('components.image_upload_remove') }}"
+ option:dropzone:upload-limit-message="{{ trans('errors.server_upload_limit') }}"
+ option:dropzone:timeout-message="{{ trans('errors.file_upload_timeout') }}"
+
class="dropzone-container text-center">
<button type="button" class="dz-message">{{ $placeholder }}</button>
</div>
\ No newline at end of file
+++ /dev/null
-<div style="display: block;" toolbox-tab-content="files"
- component="attachments"
- option:attachments:page-id="{{ $page->id ?? 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',
- ])
-
- <h4>{{ trans('entities.attachments') }}</h4>
- <div class="px-l files">
-
- <div id="file-list">
- <p class="text-muted small">{{ trans('entities.attachments_explain') }} <span class="text-warn">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
-
- <div component="tabs" refs="attachments@mainTabs" class="tab-container">
- <div class="nav-tabs">
- <button refs="tabs@toggleItems" type="button" class="selected tab-item">{{ trans('entities.attachments_items') }}</button>
- <button refs="tabs@toggleUpload" type="button" class="tab-item">{{ trans('entities.attachments_upload') }}</button>
- <button refs="tabs@toggleLinks" type="button" class="tab-item">{{ trans('entities.attachments_link') }}</button>
- </div>
- <div refs="tabs@contentItems attachments@list">
- @include('pages.attachment-list', ['attachments' => $page->attachments->all()])
- </div>
- <div refs="tabs@contentUpload" class="hiden">
- @include('components.dropzone', [
- 'placeholder' => trans('entities.attachments_dropzone'),
- 'url' => url('/attachments/upload?uploaded_to=' . $page->id)
- ])
- </div>
- <div refs="tabs@contentLinks" class="hidden">
- <p class="text-muted small">{{ trans('entities.attachments_explain_link') }}</p>
- <div class="form-group">
- <label for="attachment_link_name">{{ trans('entities.attachments_link_name') }}</label>
- <input name="attachment_link_name" id="attachment_link_name" type="text" placeholder="{{ trans('entities.attachments_link_name') }}">
- <p class="small text-neg"></p>
- </div>
- <div class="form-group">
- <label for="attachment_link_url">{{ trans('entities.attachments_link_url') }}</label>
- <input name="attachment_link_url" id="attachment_link_url" type="text" placeholder="{{ trans('entities.attachments_link_url_hint') }}">
- <p class="small text-neg"></p>
- </div>
- <button class="button">{{ trans('entities.attach') }}</button>
- </div>
- </div>
-
- </div>
-
- <div refs="attachments@editContainer" class="hidden">
- <h5>{{ trans('entities.attachments_edit_file') }}</h5>
-
- <div class="form-group">
- <label for="attachment-name-edit">{{ trans('entities.attachments_edit_file_name') }}</label>
- <input type="text" id="attachment-name-edit"
- name="attachment_name"
- placeholder="{{ trans('entities.attachments_edit_file_name') }}">
- <p class="small text-neg"></p>
- </div>
-
- <div component="tabs" class="tab-container">
- <div class="nav-tabs">
- <button refs="tabs@toggleFile" type="button" class="tab-item selected">{{ trans('entities.attachments_upload') }}</button>
- <button refs="tabs@toggleLink" type="button" class="tab-item">{{ trans('entities.attachments_set_link') }}</button>
- </div>
- <div refs="tabs@contentFile">
- @include('components.dropzone', [
- 'placeholder' => trans('entities.attachments_edit_drop_upload'),
- 'url' => url('/attachments')
- ])
- <dropzone :upload-url="getUploadUrl(fileToEdit)" :uploaded-to="pageId" placeholder="{{ trans('entities.attachments_edit_drop_upload') }}" @success="uploadSuccessUpdate"></dropzone>
- <br>
- </div>
- <div refs="tabs@contentLink" class="hidden">
- <div class="form-group">
- <label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
- <input type="text" id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" v-model="fileToEdit.link">
- <p class="small text-neg"></p>
- </div>
- </div>
- </div>
-
- <button type="button" class="button outline">{{ trans('common.back') }}</button>
- <button class="button">{{ trans('common.save') }}</button>
- </div>
-
- </div>
-</div>
\ No newline at end of file
</div>
@if(userCan('attachment-create-all'))
- @include('pages.attachment-manager', ['page' => $page])
+ @include('attachments.manager', ['page' => $page])
@endif
<div toolbox-tab-content="templates">
<div id="page-attachments" class="mb-l">
<h5>{{ trans('entities.pages_attachments') }}</h5>
<div class="body">
- @foreach($page->attachments as $attachment)
- <div class="attachment icon-list">
- <a class="icon-list-item py-xs" href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
- <span class="icon">@icon($attachment->external ? 'export' : 'file')</span>
- <span>{{ $attachment->name }}</span>
- </a>
- </div>
- @endforeach
+ @include('attachments.list', ['attachments' => $page->attachments])
</div>
</div>
@endif
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');