mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Migrated attachment manager to vue
This commit is contained in:
parent
a04b31866d
commit
afc66b3c3d
@ -145,202 +145,6 @@ module.exports = function (ngApp, events) {
|
|||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
ngApp.controller('PageAttachmentController', ['$scope', '$http', '$attrs',
|
|
||||||
function ($scope, $http, $attrs) {
|
|
||||||
|
|
||||||
const pageId = $scope.uploadedTo = $attrs.pageId;
|
|
||||||
let currentOrder = '';
|
|
||||||
$scope.files = [];
|
|
||||||
$scope.editFile = false;
|
|
||||||
$scope.file = getCleanFile();
|
|
||||||
$scope.errors = {
|
|
||||||
link: {},
|
|
||||||
edit: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCleanFile() {
|
|
||||||
return {
|
|
||||||
page_id: pageId
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Angular-UI-Sort options
|
|
||||||
$scope.sortOptions = {
|
|
||||||
handle: '.handle',
|
|
||||||
items: '> tr',
|
|
||||||
containment: "parent",
|
|
||||||
axis: "y",
|
|
||||||
stop: sortUpdate,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event listener for sort changes.
|
|
||||||
* Updates the file ordering on the server.
|
|
||||||
* @param event
|
|
||||||
* @param ui
|
|
||||||
*/
|
|
||||||
function sortUpdate(event, ui) {
|
|
||||||
let newOrder = $scope.files.map(file => {return file.id}).join(':');
|
|
||||||
if (newOrder === currentOrder) return;
|
|
||||||
|
|
||||||
currentOrder = newOrder;
|
|
||||||
$http.put(window.baseUrl(`/attachments/sort/page/${pageId}`), {files: $scope.files}).then(resp => {
|
|
||||||
events.emit('success', resp.data.message);
|
|
||||||
}, checkError('sort'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by dropzone to get the endpoint to upload to.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
$scope.getUploadUrl = function (file) {
|
|
||||||
let suffix = (typeof file !== 'undefined') ? `/${file.id}` : '';
|
|
||||||
return window.baseUrl(`/attachments/upload${suffix}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get files for the current page from the server.
|
|
||||||
*/
|
|
||||||
function getFiles() {
|
|
||||||
let url = window.baseUrl(`/attachments/get/page/${pageId}`);
|
|
||||||
$http.get(url).then(resp => {
|
|
||||||
$scope.files = resp.data;
|
|
||||||
currentOrder = resp.data.map(file => {return file.id}).join(':');
|
|
||||||
}, checkError('get'));
|
|
||||||
}
|
|
||||||
getFiles();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs on file upload, Adds an file to local file list
|
|
||||||
* and shows a success message to the user.
|
|
||||||
* @param file
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
$scope.uploadSuccess = function (file, data) {
|
|
||||||
$scope.$apply(() => {
|
|
||||||
$scope.files.push(data);
|
|
||||||
});
|
|
||||||
events.emit('success', trans('entities.attachments_file_uploaded'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload and overwrite an existing file.
|
|
||||||
* @param file
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
$scope.uploadSuccessUpdate = function (file, data) {
|
|
||||||
$scope.$apply(() => {
|
|
||||||
let search = filesIndexOf(data);
|
|
||||||
if (search !== -1) $scope.files[search] = data;
|
|
||||||
|
|
||||||
if ($scope.editFile) {
|
|
||||||
$scope.editFile = angular.copy(data);
|
|
||||||
data.link = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
events.emit('success', trans('entities.attachments_file_updated'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a file from the server and, on success, the local listing.
|
|
||||||
* @param file
|
|
||||||
*/
|
|
||||||
$scope.deleteFile = function(file) {
|
|
||||||
if (!file.deleting) {
|
|
||||||
file.deleting = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$http.delete(window.baseUrl(`/attachments/${file.id}`)).then(resp => {
|
|
||||||
events.emit('success', resp.data.message);
|
|
||||||
$scope.files.splice($scope.files.indexOf(file), 1);
|
|
||||||
}, checkError('delete'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach a link to a page.
|
|
||||||
* @param file
|
|
||||||
*/
|
|
||||||
$scope.attachLinkSubmit = function(file) {
|
|
||||||
file.uploaded_to = pageId;
|
|
||||||
$http.post(window.baseUrl('/attachments/link'), file).then(resp => {
|
|
||||||
$scope.files.push(resp.data);
|
|
||||||
events.emit('success', trans('entities.attachments_link_attached'));
|
|
||||||
$scope.file = getCleanFile();
|
|
||||||
}, checkError('link'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the edit mode for a file.
|
|
||||||
* @param file
|
|
||||||
*/
|
|
||||||
$scope.startEdit = function(file) {
|
|
||||||
$scope.editFile = angular.copy(file);
|
|
||||||
$scope.editFile.link = (file.external) ? file.path : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel edit mode
|
|
||||||
*/
|
|
||||||
$scope.cancelEdit = function() {
|
|
||||||
$scope.editFile = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the name and link of a file.
|
|
||||||
* @param file
|
|
||||||
*/
|
|
||||||
$scope.updateFile = function(file) {
|
|
||||||
$http.put(window.baseUrl(`/attachments/${file.id}`), file).then(resp => {
|
|
||||||
let search = filesIndexOf(resp.data);
|
|
||||||
if (search !== -1) $scope.files[search] = resp.data;
|
|
||||||
|
|
||||||
if ($scope.editFile && !file.external) {
|
|
||||||
$scope.editFile.link = '';
|
|
||||||
}
|
|
||||||
$scope.editFile = false;
|
|
||||||
events.emit('success', trans('entities.attachments_updated_success'));
|
|
||||||
}, checkError('edit'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the url of a file.
|
|
||||||
*/
|
|
||||||
$scope.getFileUrl = function(file) {
|
|
||||||
return window.baseUrl('/attachments/' + file.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search the local files via another file object.
|
|
||||||
* Used to search via object copies.
|
|
||||||
* @param file
|
|
||||||
* @returns int
|
|
||||||
*/
|
|
||||||
function filesIndexOf(file) {
|
|
||||||
for (let i = 0; i < $scope.files.length; i++) {
|
|
||||||
if ($scope.files[i].id == file.id) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for an error response in a ajax request.
|
|
||||||
* @param errorGroupName
|
|
||||||
*/
|
|
||||||
function checkError(errorGroupName) {
|
|
||||||
$scope.errors[errorGroupName] = {};
|
|
||||||
return function(response) {
|
|
||||||
if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') {
|
|
||||||
events.emit('error', response.data.error);
|
|
||||||
}
|
|
||||||
if (typeof response.data !== 'undefined' && typeof response.data.validation !== 'undefined') {
|
|
||||||
$scope.errors[errorGroupName] = response.data.validation;
|
|
||||||
console.log($scope.errors[errorGroupName])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}]);
|
|
||||||
|
|
||||||
// Controller used to reply to and add new comments
|
// Controller used to reply to and add new comments
|
||||||
ngApp.controller('CommentReplyController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {
|
ngApp.controller('CommentReplyController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {
|
||||||
const MarkdownIt = require("markdown-it");
|
const MarkdownIt = require("markdown-it");
|
||||||
|
@ -1,119 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
const DropZone = require("dropzone");
|
|
||||||
const MarkdownIt = require("markdown-it");
|
const MarkdownIt = require("markdown-it");
|
||||||
const mdTasksLists = require('markdown-it-task-lists');
|
const mdTasksLists = require('markdown-it-task-lists');
|
||||||
const code = require('./code');
|
const code = require('./code');
|
||||||
|
|
||||||
module.exports = function (ngApp, events) {
|
module.exports = function (ngApp, events) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Common tab controls using simple jQuery functions.
|
|
||||||
*/
|
|
||||||
ngApp.directive('tabContainer', function() {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
link: function (scope, element, attrs) {
|
|
||||||
const $content = element.find('[tab-content]');
|
|
||||||
const $buttons = element.find('[tab-button]');
|
|
||||||
|
|
||||||
if (attrs.tabContainer) {
|
|
||||||
let initial = attrs.tabContainer;
|
|
||||||
$buttons.filter(`[tab-button="${initial}"]`).addClass('selected');
|
|
||||||
$content.hide().filter(`[tab-content="${initial}"]`).show();
|
|
||||||
} else {
|
|
||||||
$content.hide().first().show();
|
|
||||||
$buttons.first().addClass('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
$buttons.click(function() {
|
|
||||||
let clickedTab = $(this);
|
|
||||||
$buttons.removeClass('selected');
|
|
||||||
$content.hide();
|
|
||||||
let name = clickedTab.addClass('selected').attr('tab-button');
|
|
||||||
$content.filter(`[tab-content="${name}"]`).show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sub form component to allow inner-form sections to act like their own forms.
|
|
||||||
*/
|
|
||||||
ngApp.directive('subForm', function() {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
link: function (scope, element, attrs) {
|
|
||||||
element.on('keypress', e => {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
submitEvent(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
element.find('button[type="submit"]').click(submitEvent);
|
|
||||||
|
|
||||||
function submitEvent(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (attrs.subForm) scope.$eval(attrs.subForm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DropZone
|
|
||||||
* Used for uploading images
|
|
||||||
*/
|
|
||||||
ngApp.directive('dropZone', [function () {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
template: `
|
|
||||||
<div class="dropzone-container">
|
|
||||||
<div class="dz-message">{{message}}</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
scope: {
|
|
||||||
uploadUrl: '@',
|
|
||||||
eventSuccess: '=',
|
|
||||||
eventError: '=',
|
|
||||||
uploadedTo: '@',
|
|
||||||
},
|
|
||||||
link: function (scope, element, attrs) {
|
|
||||||
scope.message = attrs.message;
|
|
||||||
if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder;
|
|
||||||
let dropZone = new DropZone(element[0].querySelector('.dropzone-container'), {
|
|
||||||
url: scope.uploadUrl,
|
|
||||||
init: function () {
|
|
||||||
let dz = this;
|
|
||||||
dz.on('sending', function (file, xhr, data) {
|
|
||||||
let token = window.document.querySelector('meta[name=token]').getAttribute('content');
|
|
||||||
data.append('_token', token);
|
|
||||||
let uploadedTo = typeof scope.uploadedTo === 'undefined' ? 0 : scope.uploadedTo;
|
|
||||||
data.append('uploaded_to', uploadedTo);
|
|
||||||
});
|
|
||||||
if (typeof scope.eventSuccess !== 'undefined') dz.on('success', scope.eventSuccess);
|
|
||||||
dz.on('success', function (file, data) {
|
|
||||||
$(file.previewElement).fadeOut(400, function () {
|
|
||||||
dz.removeFile(file);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (typeof scope.eventError !== 'undefined') dz.on('error', scope.eventError);
|
|
||||||
dz.on('error', function (file, errorMessage, xhr) {
|
|
||||||
console.log(errorMessage);
|
|
||||||
console.log(xhr);
|
|
||||||
function setMessage(message) {
|
|
||||||
$(file.previewElement).find('[data-dz-errormessage]').text(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xhr.status === 413) setMessage(trans('errors.server_upload_limit'));
|
|
||||||
if (errorMessage.file) setMessage(errorMessage.file[0]);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TinyMCE
|
* TinyMCE
|
||||||
* An angular wrapper around the tinyMCE editor.
|
* An angular wrapper around the tinyMCE editor.
|
||||||
|
@ -9,34 +9,6 @@ window.baseUrl = function(path) {
|
|||||||
return basePath + '/' + path;
|
return basePath + '/' + path;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Vue = require("vue");
|
|
||||||
const axios = require("axios");
|
|
||||||
|
|
||||||
let axiosInstance = axios.create({
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name=token]').getAttribute('content'),
|
|
||||||
'baseURL': window.baseUrl('')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.$http = axiosInstance;
|
|
||||||
Vue.prototype.$http = axiosInstance;
|
|
||||||
|
|
||||||
|
|
||||||
// AngularJS - Create application and load components
|
|
||||||
const angular = require("angular");
|
|
||||||
require("angular-resource");
|
|
||||||
require("angular-animate");
|
|
||||||
require("angular-sanitize");
|
|
||||||
require("angular-ui-sortable");
|
|
||||||
|
|
||||||
let ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
|
|
||||||
|
|
||||||
// Translation setup
|
|
||||||
// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
|
|
||||||
const Translations = require("./translations");
|
|
||||||
let translator = new Translations(window.translations);
|
|
||||||
window.trans = translator.get.bind(translator);
|
|
||||||
|
|
||||||
// Global Event System
|
// Global Event System
|
||||||
class EventManager {
|
class EventManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -61,8 +33,45 @@ class EventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.Events = new EventManager();
|
window.Events = new EventManager();
|
||||||
|
|
||||||
|
const Vue = require("vue");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
let axiosInstance = axios.create({
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name=token]').getAttribute('content'),
|
||||||
|
'baseURL': window.baseUrl('')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
axiosInstance.interceptors.request.use(resp => {
|
||||||
|
return resp;
|
||||||
|
}, err => {
|
||||||
|
if (typeof err.response === "undefined" || typeof err.response.data === "undefined") return Promise.reject(err);
|
||||||
|
if (typeof err.response.data.error !== "undefined") window.Events.emit('error', err.response.data.error);
|
||||||
|
if (typeof err.response.data.message !== "undefined") window.Events.emit('error', err.response.data.message);
|
||||||
|
});
|
||||||
|
window.$http = axiosInstance;
|
||||||
|
|
||||||
|
Vue.prototype.$http = axiosInstance;
|
||||||
Vue.prototype.$events = window.Events;
|
Vue.prototype.$events = window.Events;
|
||||||
|
|
||||||
|
|
||||||
|
// AngularJS - Create application and load components
|
||||||
|
const angular = require("angular");
|
||||||
|
require("angular-resource");
|
||||||
|
require("angular-animate");
|
||||||
|
require("angular-sanitize");
|
||||||
|
require("angular-ui-sortable");
|
||||||
|
|
||||||
|
let ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
|
||||||
|
|
||||||
|
// Translation setup
|
||||||
|
// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
|
||||||
|
const Translations = require("./translations");
|
||||||
|
let translator = new Translations(window.translations);
|
||||||
|
window.trans = translator.get.bind(translator);
|
||||||
|
|
||||||
|
|
||||||
require("./vues/vues");
|
require("./vues/vues");
|
||||||
require("./components");
|
require("./components");
|
||||||
|
|
||||||
|
138
resources/assets/js/vues/attachment-manager.js
Normal file
138
resources/assets/js/vues/attachment-manager.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
const draggable = require('vuedraggable');
|
||||||
|
const dropzone = require('./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) {
|
||||||
|
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 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) {
|
||||||
|
console.error(err);
|
||||||
|
if (typeof err.response.data === "undefined" && typeof err.response.data.validation === "undefined") return;
|
||||||
|
this.errors[groupName] = err.response.data.validation;
|
||||||
|
console.log(this.errors[groupName]);
|
||||||
|
},
|
||||||
|
|
||||||
|
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.$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data, methods, mounted, components,
|
||||||
|
};
|
@ -127,8 +127,6 @@ const methods = {
|
|||||||
message += errors[key].join('\n');
|
message += errors[key].join('\n');
|
||||||
});
|
});
|
||||||
this.$events.emit('error', message);
|
this.$events.emit('error', message);
|
||||||
} else if (error.response.status === 403) {
|
|
||||||
this.$events.emit('error', error.response.data.error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -144,8 +142,6 @@ const methods = {
|
|||||||
}).catch(error=> {
|
}).catch(error=> {
|
||||||
if (error.response.status === 400) {
|
if (error.response.status === 400) {
|
||||||
this.dependantPages = error.response.data;
|
this.dependantPages = error.response.data;
|
||||||
} else if (error.response.status === 403) {
|
|
||||||
this.$events.emit('error', error.response.data.error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -10,14 +10,15 @@ let vueMapping = {
|
|||||||
'code-editor': require('./code-editor'),
|
'code-editor': require('./code-editor'),
|
||||||
'image-manager': require('./image-manager'),
|
'image-manager': require('./image-manager'),
|
||||||
'tag-manager': require('./tag-manager'),
|
'tag-manager': require('./tag-manager'),
|
||||||
|
'attachment-manager': require('./attachment-manager'),
|
||||||
};
|
};
|
||||||
|
|
||||||
window.vues = {};
|
window.vues = {};
|
||||||
|
|
||||||
Object.keys(vueMapping).forEach(id => {
|
let ids = Object.keys(vueMapping);
|
||||||
if (exists(id)) {
|
for (let i = 0, len = ids.length; i < len; i++) {
|
||||||
let config = vueMapping[id];
|
if (!exists(ids[i])) continue;
|
||||||
config.el = '#' + id;
|
let config = vueMapping[ids[i]];
|
||||||
window.vues[id] = new Vue(config);
|
config.el = '#' + ids[i];
|
||||||
|
window.vues[ids[i]] = new Vue(config);
|
||||||
}
|
}
|
||||||
});
|
|
@ -512,7 +512,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[tab-container] .nav-tabs {
|
.tab-container .nav-tabs {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid #DDD;
|
border-bottom: 1px solid #DDD;
|
||||||
margin-bottom: $-m;
|
margin-bottom: $-m;
|
||||||
|
@ -59,18 +59,9 @@ table.list-table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.file-table {
|
|
||||||
@extend .no-style;
|
|
||||||
td {
|
|
||||||
padding: $-xs;
|
|
||||||
}
|
|
||||||
.ui-sortable-helper {
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fake-table {
|
.fake-table {
|
||||||
display: table;
|
display: table;
|
||||||
|
width: 100%;
|
||||||
> div {
|
> div {
|
||||||
display: table-row-group;
|
display: table-row-group;
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
<div class="padded tags">
|
<div class="padded tags">
|
||||||
<p class="muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
|
<p class="muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
|
||||||
|
|
||||||
<draggable class="fake-table no-style tag-table" :options="{handle: '.handle'}" :list="tags" element="div" style="width: 100%;">
|
<draggable class="fake-table no-style tag-table" :options="{handle: '.handle'}" :list="tags" element="div">
|
||||||
<transition-group name="test" tag="div">
|
<transition-group tag="div">
|
||||||
<div v-for="(tag, i) in tags" :key="tag.key">
|
<div v-for="(tag, i) in tags" :key="tag.key">
|
||||||
<div width="20" class="handle" ><i class="zmdi zmdi-menu"></i></div>
|
<div width="20" class="handle" ><i class="zmdi zmdi-menu"></i></div>
|
||||||
<div>
|
<div>
|
||||||
@ -47,93 +47,93 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(userCan('attachment-create-all'))
|
@if(userCan('attachment-create-all'))
|
||||||
<div toolbox-tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
|
<div toolbox-tab-content="files" id="attachment-manager" page-id="{{ $page->id or 0 }}">
|
||||||
<h4>{{ trans('entities.attachments') }}</h4>
|
<h4>{{ trans('entities.attachments') }}</h4>
|
||||||
<div class="padded files">
|
<div class="padded files">
|
||||||
|
|
||||||
<div id="file-list" ng-show="!editFile">
|
<div id="file-list" v-show="!fileToEdit">
|
||||||
<p class="muted small">{{ trans('entities.attachments_explain') }} <span class="secondary">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
|
<p class="muted small">{{ trans('entities.attachments_explain') }} <span class="secondary">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
|
||||||
|
|
||||||
<div tab-container>
|
<div class="tab-container">
|
||||||
<div class="nav-tabs">
|
<div class="nav-tabs">
|
||||||
<div tab-button="list" class="tab-item">{{ trans('entities.attachments_items') }}</div>
|
<div @click="tab = 'list'" :class="{selected: tab === 'list'}" class="tab-item">{{ trans('entities.attachments_items') }}</div>
|
||||||
<div tab-button="file" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
|
<div @click="tab = 'file'" :class="{selected: tab === 'file'}" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
|
||||||
<div tab-button="link" class="tab-item">{{ trans('entities.attachments_link') }}</div>
|
<div @click="tab = 'link'" :class="{selected: tab === 'link'}" class="tab-item">{{ trans('entities.attachments_link') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div tab-content="list">
|
<div v-show="tab === 'list'">
|
||||||
<table class="file-table" style="width: 100%;">
|
<draggable class="fake-table no-style " style="width: 100%;" :options="{handle: '.handle'}" @change="fileSortUpdate" :list="files" element="div">
|
||||||
<tbody ui-sortable="sortOptions" ng-model="files" >
|
<transition-group tag="div">
|
||||||
<tr ng-repeat="file in files track by $index">
|
<div v-for="(file, index) in files" :key="file.id">
|
||||||
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
|
<div width="20" ><i class="handle zmdi zmdi-menu"></i></div>
|
||||||
<td>
|
<div>
|
||||||
<a ng-href="@{{getFileUrl(file)}}" target="_blank" ng-bind="file.name"></a>
|
<a :href="getFileUrl(file)" target="_blank" v-text="file.name"></a>
|
||||||
<div ng-if="file.deleting">
|
<div v-if="file.deleting">
|
||||||
<span class="neg small">{{ trans('entities.attachments_delete_confirm') }}</span>
|
<span class="neg small">{{ trans('entities.attachments_delete_confirm') }}</span>
|
||||||
<br>
|
<br>
|
||||||
<span class="text-primary small" ng-click="file.deleting=false;">{{ trans('common.cancel') }}</span>
|
<span class="text-primary small" @click="file.deleting = false;">{{ trans('common.cancel') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td width="10" ng-click="startEdit(file)" class="text-center text-primary" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td>
|
<div width="10" @click="startEdit(file)" class="text-center text-primary" style="padding: 0;"><i class="zmdi zmdi-edit"></i></div>
|
||||||
<td width="5"></td>
|
<div width="5"></div>
|
||||||
<td width="10" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
|
<div width="10" @click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></div>
|
||||||
</tr>
|
</div>
|
||||||
</tbody>
|
</transition-group>
|
||||||
</table>
|
</draggable>
|
||||||
<p class="small muted" ng-if="files.length == 0">
|
<p class="small muted" v-if="files.length === 0">
|
||||||
{{ trans('entities.attachments_no_files') }}
|
{{ trans('entities.attachments_no_files') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div tab-content="file">
|
<div v-show="tab === 'file'">
|
||||||
<drop-zone message="{{ trans('entities.attachments_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
|
<dropzone placeholder="{{ trans('entities.attachments_dropzone') }}" :upload-url="getUploadUrl()" :uploaded-to="pageId" @success="uploadSuccess"></dropzone>
|
||||||
</div>
|
</div>
|
||||||
<div tab-content="link" sub-form="attachLinkSubmit(file)">
|
<div v-show="tab === 'link'" @keypress.enter.prevent="attachNewLink(file)">
|
||||||
<p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
|
<p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
|
<label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
|
||||||
<input placeholder="{{ trans('entities.attachments_link_name') }}" ng-model="file.name">
|
<input type="text" placeholder="{{ trans('entities.attachments_link_name') }}" v-model="file.name">
|
||||||
<p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p>
|
<p class="small neg" v-for="error in errors.link.name" v-text="error"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
|
<label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
|
||||||
<input placeholder="{{ trans('entities.attachments_link_url_hint') }}" ng-model="file.link">
|
<input type="text" placeholder="{{ trans('entities.attachments_link_url_hint') }}" v-model="file.link">
|
||||||
<p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p>
|
<p class="small neg" v-for="error in errors.link.link" v-text="error"></p>
|
||||||
</div>
|
</div>
|
||||||
<button class="button pos">{{ trans('entities.attach') }}</button>
|
<button @click.prevent="attachNewLink(file)" class="button pos">{{ trans('entities.attach') }}</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="file-edit" ng-if="editFile" sub-form="updateFile(editFile)">
|
<div id="file-edit" v-if="fileToEdit" @keypress.enter.prevent="updateFile(fileToEdit)">
|
||||||
<h5>{{ trans('entities.attachments_edit_file') }}</h5>
|
<h5>{{ trans('entities.attachments_edit_file') }}</h5>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-name-edit">{{ trans('entities.attachments_edit_file_name') }}</label>
|
<label for="attachment-name-edit">{{ trans('entities.attachments_edit_file_name') }}</label>
|
||||||
<input type="text" id="attachment-name-edit" placeholder="{{ trans('entities.attachments_edit_file_name') }}" ng-model="editFile.name">
|
<input type="text" id="attachment-name-edit" placeholder="{{ trans('entities.attachments_edit_file_name') }}" v-model="fileToEdit.name">
|
||||||
<p class="small neg" ng-repeat="error in errors.edit.name" ng-bind="error"></p>
|
<p class="small neg" v-for="error in errors.edit.name" v-text="error"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div tab-container="@{{ editFile.external ? 'link' : 'file' }}">
|
<div class="tab-container">
|
||||||
<div class="nav-tabs">
|
<div class="nav-tabs">
|
||||||
<div tab-button="file" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
|
<div @click="editTab = 'file'" :class="{selected: editTab === 'file'}" class="tab-item">{{ trans('entities.attachments_upload') }}</div>
|
||||||
<div tab-button="link" class="tab-item">{{ trans('entities.attachments_set_link') }}</div>
|
<div @click="editTab = 'link'" :class="{selected: editTab === 'link'}" class="tab-item">{{ trans('entities.attachments_set_link') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div tab-content="file">
|
<div v-if="editTab === 'file'">
|
||||||
<drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="{{ trans('entities.attachments_edit_drop_upload') }}" event-success="uploadSuccessUpdate"></drop-zone>
|
<dropzone :upload-url="getUploadUrl(fileToEdit)" :uploaded-to="pageId" placeholder="{{ trans('entities.attachments_edit_drop_upload') }}" @success="uploadSuccessUpdate"></dropzone>
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
<div tab-content="link">
|
<div v-if="editTab === 'link'">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
|
<label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
|
||||||
<input id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" ng-model="editFile.link">
|
<input type="text" id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" v-model="fileToEdit.link">
|
||||||
<p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p>
|
<p class="small neg" v-for="error in errors.edit.link" v-text="error"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="button" ng-click="cancelEdit()">{{ trans('common.back') }}</button>
|
<button type="button" class="button" @click="cancelEdit">{{ trans('common.back') }}</button>
|
||||||
<button class="button pos">{{ trans('common.save') }}</button>
|
<button @click.enter.prevent="updateFile(fileToEdit)" class="button pos">{{ trans('common.save') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user