add file manager
This commit is contained in:
parent
0d924dbf8a
commit
77e989de10
|
@ -19,3 +19,4 @@ require('./modules/form-collection.js')();
|
|||
require('./modules/datepicker.js')();
|
||||
require('./modules/sortable.js')();
|
||||
require('./modules/batch.js')();
|
||||
require('./modules/file-manager.js')();
|
||||
|
|
45
assets/js/admin/components/file-manager/FileIcon.vue
Normal file
45
assets/js/admin/components/file-manager/FileIcon.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<span v-bind:class="getIcon(mime)"></span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const map = {
|
||||
'fa-file-pdf': ['application/pdf'],
|
||||
'fa-file-image': ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'],
|
||||
'fa-file-audio': ['application/ogg', 'audio/mp3', 'audio/mpeg', 'audio/wav'],
|
||||
'fa-file-archive': ['application/zip', 'multipart/x-zip', 'application/rar', 'application/x-rar-compressed', 'application/x-zip-compressed', 'application/tar', 'application/x-tar'],
|
||||
'fa-file-alt': ['application/rtf'],
|
||||
'fa-file-excel': ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
||||
'fa-file-powerpoint': ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
||||
'fa-file-video': ['video/x-msvideo', 'video/mpeg'],
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'FileIcon',
|
||||
methods: {
|
||||
getIcon(mime) {
|
||||
let icons = ['fa']
|
||||
let iconFound = false
|
||||
|
||||
for (let icon in map) {
|
||||
if (map[icon].indexOf(mime) !== -1) {
|
||||
iconFound = true
|
||||
icons.push(icon)
|
||||
}
|
||||
}
|
||||
|
||||
if (!iconFound) {
|
||||
icons.push('fa-file')
|
||||
}
|
||||
|
||||
return icons
|
||||
},
|
||||
},
|
||||
props: {
|
||||
mime: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
23
assets/js/admin/components/file-manager/FileManager.vue
Normal file
23
assets/js/admin/components/file-manager/FileManager.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<Files />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Files from './Files'
|
||||
|
||||
export default {
|
||||
name: "FileManager",
|
||||
components: {
|
||||
Files,
|
||||
}
|
||||
}
|
||||
</script>
|
284
assets/js/admin/components/file-manager/Files.vue
Normal file
284
assets/js/admin/components/file-manager/Files.vue
Normal file
|
@ -0,0 +1,284 @@
|
|||
<template>
|
||||
<div>
|
||||
<nav aria-label="breadcrumb bg-light">
|
||||
<div class="float-right">
|
||||
</div>
|
||||
|
||||
<ol class="breadcrumb mb-0 float-right file-manager-views">
|
||||
<li class="breadcrumb-item">
|
||||
<span class="fa fa-grip-horizontal" v-on:click="setView('grid')"></span>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<span class="fa fa-list" v-on:click="setView('list')"></span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<ol class="breadcrumb mb-0 float-right file-manager-actions">
|
||||
<li class="breadcrumb-item">
|
||||
<span class="fa fa-upload text-primary" v-bind:data-modal="generateUploadLink(directory)"></span>
|
||||
<span class="fa fa-folder-plus text-primary" v-bind:data-modal="generateNewDirectoryLink(directory)"></span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item" v-for="item in breadcrumb">
|
||||
<a href="#" v-on:click="setDirectory(item.path)" v-html="item.label"></a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="card-deck" v-if="view == 'grid'">
|
||||
<div v-if="parent" class="card mt-3 ml-3 mb-3 border-0">
|
||||
<div class="card-body p-2">
|
||||
<div class="card-text" v-on:dblclick="setDirectory(parent)">
|
||||
<div class="text-center display-4 text-warning">
|
||||
<span class="fa fa-folder"></span>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
..
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="item in directories" class="card mt-3 ml-3 mb-3 border-0">
|
||||
<div class="card-body p-2">
|
||||
<div class="card-text" v-on:dblclick="setDirectory(item.path)" v-bind:data-modal="generateInfoLink(item, true)">
|
||||
<div class="text-center">
|
||||
<div class="display-4 text-warning">
|
||||
<span class="fa fa-folder"></span>
|
||||
</div>
|
||||
|
||||
<div v-if="item.locked" class="file-manager-grid-lock">
|
||||
<span class="btn btn-sm">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<span v-html="item.basename"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="item in files" class="card mt-3 ml-3 mb-3 border-0" v-bind:data-modal="generateInfoLink(item)">
|
||||
<div class="card-body p-2">
|
||||
<div class="card-text">
|
||||
<div class="text-center">
|
||||
<div class="display-4 text-muted">
|
||||
<FileIcon v-bind:mime="item.mime" />
|
||||
</div>
|
||||
|
||||
<div v-if="item.locked" class="file-manager-grid-lock">
|
||||
<span class="btn btn-sm">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<span v-html="item.basename"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" v-if="view == 'list'">
|
||||
<table class="table">
|
||||
<tr v-if="parent" v-on:dblclick="setDirectory(parent)">
|
||||
<td width="10">
|
||||
<span class="fa fa-folder text-warning"></span>
|
||||
</td>
|
||||
<td>
|
||||
..
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-for="item in directories" v-on:dblclick="setDirectory(item.path)" v-bind:data-modal="generateInfoLink(item, true)">
|
||||
<td width="10">
|
||||
<span class="fa fa-folder text-warning"></span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="item.locked" class="float-right">
|
||||
<span class="btn btn-sm btn-light">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span v-html="item.basename"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="item in files">
|
||||
<td width="10">
|
||||
<FileIcon v-bind:mime="item.mime" />
|
||||
</td>
|
||||
<td v-bind:data-modal="generateInfoLink(item)">
|
||||
<div v-if="item.locked" class="float-right">
|
||||
<span class="btn btn-sm btn-light">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span v-html="item.basename"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
margin-right: 5px;
|
||||
flex: 0 0 170px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-manager-views {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-manager-grid-lock {
|
||||
margin-top: -26px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.file-manager-actions .fa {
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const axios = require('axios').default
|
||||
const routes = require('../../../../../public/js/fos_js_routes.json')
|
||||
|
||||
import Routing from '../../../../../vendor/friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js';
|
||||
import FileIcon from './FileIcon';
|
||||
|
||||
export default {
|
||||
name: "Files",
|
||||
components: {
|
||||
FileIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
view: 'list',
|
||||
directory: null,
|
||||
directories: [],
|
||||
breadcrumb: [],
|
||||
files: [],
|
||||
parent: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setDirectory(directory) {
|
||||
this.directory = directory
|
||||
},
|
||||
setView(view) {
|
||||
this.view = view
|
||||
|
||||
localStorage.setItem('file-manager.view', view)
|
||||
},
|
||||
generateInfoLink(item, directory) {
|
||||
if (directory) {
|
||||
return Routing.generate('admin_file_manager_info', {
|
||||
file: item.path
|
||||
})
|
||||
} else {
|
||||
return Routing.generate('admin_file_manager_info', {
|
||||
file: item.path + '/' + item.basename
|
||||
})
|
||||
}
|
||||
},
|
||||
generateUploadLink(directory) {
|
||||
return Routing.generate('admin_file_manager_upload', {
|
||||
file: directory
|
||||
})
|
||||
},
|
||||
generateNewDirectoryLink(directory) {
|
||||
return Routing.generate('admin_file_manager_directory_new', {
|
||||
file: directory
|
||||
})
|
||||
},
|
||||
buildBreadcrum(elements) {
|
||||
let path = '/'
|
||||
this.breadcrumb = []
|
||||
|
||||
for (let i in elements) {
|
||||
const element = elements[i]
|
||||
|
||||
if (element !== '/') {
|
||||
path = path + '/' + element
|
||||
|
||||
this.breadcrumb.push({
|
||||
path: path,
|
||||
label: element,
|
||||
})
|
||||
} else {
|
||||
this.breadcrumb.push({
|
||||
path: '/',
|
||||
label: 'Files',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Routing.setRoutingData(routes)
|
||||
let view = localStorage.getItem('file-manager.view')
|
||||
|
||||
if (['grid', 'list'].indexOf(view) !== -1) {
|
||||
this.view = view
|
||||
}
|
||||
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
|
||||
if (query.has('path')) {
|
||||
this.setDirectory(query.get('path'))
|
||||
} else {
|
||||
this.setDirectory('/')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
directory(directory) {
|
||||
axios.get(Routing.generate('admin_file_manager_api_directory', {
|
||||
directory: this.directory
|
||||
}))
|
||||
.then((response) => {
|
||||
this.buildBreadcrum(response.data.breadcrumb)
|
||||
this.parent = response.data.parent
|
||||
this.directories = response.data.directories
|
||||
this.files = response.data.files
|
||||
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
query.set('path', directory)
|
||||
|
||||
history.pushState(
|
||||
null,
|
||||
'',
|
||||
window.location.pathname + '?' + query.toString()
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
alert('An error occured')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -96,5 +96,5 @@ module.exports = function() {
|
|||
const config = {attributes: false, childList: true, subtree: true};
|
||||
observer.observe(document.querySelector('body'), config);
|
||||
|
||||
$(window).ready(doInitEditor);
|
||||
doInitEditor();
|
||||
};
|
||||
|
|
18
assets/js/admin/modules/file-manager.js
Normal file
18
assets/js/admin/modules/file-manager.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
// file-manager
|
||||
const Vue = require('vue').default
|
||||
|
||||
const FileManager = require('../components/file-manager/FileManager').default
|
||||
|
||||
module.exports = () => {
|
||||
if (!document.getElementById('file-manager')) {
|
||||
return
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: '#file-manager',
|
||||
template: '<FileManager />',
|
||||
components: {
|
||||
FileManager
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
const $ = require('jquery');
|
||||
|
||||
module.exports = function() {
|
||||
$('*[data-form-confirm]').submit(function(e) {
|
||||
$('body').on('submit', '*[data-form-confirm]', function(e) {
|
||||
let message = $(this).attr('data-form-confirm');
|
||||
|
||||
if (!message) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const $ = require('jquery');
|
||||
|
||||
module.exports = function() {
|
||||
$('.custom-file-input').on('change', function(event) {
|
||||
$('body').on('change', '.custom-file-input', function(event) {
|
||||
let inputFile = event.currentTarget;
|
||||
|
||||
$(inputFile).parent()
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
const $ = require('jquery');
|
||||
|
||||
module.exports = function() {
|
||||
let click = 0;
|
||||
|
||||
$('body').on('click', '*[data-modal]', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
++click;
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (click !== 1) {
|
||||
click = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
click = 0;
|
||||
|
||||
let container = $('#modal-container');
|
||||
const body = $('body')
|
||||
|
||||
|
@ -20,12 +33,18 @@ module.exports = function() {
|
|||
|
||||
container.html('');
|
||||
|
||||
const url = $(e.target).attr('data-modal');
|
||||
let url = $(e.target).attr('data-modal');
|
||||
|
||||
if (!url) {
|
||||
url = $(e.target).parents('*[data-modal]').first().attr('data-modal');
|
||||
}
|
||||
|
||||
$(container).modal('show');
|
||||
|
||||
container.load(url, function() {
|
||||
loader.remove()
|
||||
});
|
||||
}, 250)
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"doctrine/doctrine-bundle": "^2.2",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.8",
|
||||
"friendsofsymfony/jsrouting-bundle": "^2.7",
|
||||
"knplabs/doctrine-behaviors": "^2.2",
|
||||
"knplabs/knp-paginator-bundle": "^5.4",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
|
@ -23,6 +24,7 @@
|
|||
"scheb/2fa-qr-code": "^5.7",
|
||||
"sensio/framework-extra-bundle": "^6.1",
|
||||
"sensiolabs/ansi-to-html": "^1.2",
|
||||
"spe/filesize-extension-bundle": "~2.0.0",
|
||||
"stof/doctrine-extensions-bundle": "^1.6",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "5.2.*",
|
||||
|
|
|
@ -20,4 +20,5 @@ return [
|
|||
App\Core\Bundle\CoreBundle::class => ['all' => true],
|
||||
App\Bundle\AppBundle::class => ['all' => true],
|
||||
Knp\DoctrineBehaviors\DoctrineBehaviorsBundle::class => ['all' => true],
|
||||
SPE\FilesizeExtensionBundle\SPEFilesizeExtensionBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -9,3 +9,26 @@ core:
|
|||
name: 'Simple page'
|
||||
templates:
|
||||
- {name: "Default", file: "page/simple/default.html.twig"}
|
||||
file_manager:
|
||||
# mimes:
|
||||
# - image/png
|
||||
# - image/jpg
|
||||
# - image/jpeg
|
||||
# - image/gif
|
||||
# - application/pdf
|
||||
# - application/ogg
|
||||
# - video/mp4
|
||||
# - application/zip
|
||||
# - multipart/x-zip
|
||||
# - application/rar
|
||||
# - application/x-rar-compressed
|
||||
# - application/x-zip-compressed
|
||||
# - application/tar
|
||||
# - application/x-tar
|
||||
# - text/plain
|
||||
# - text/x-asm
|
||||
# - application/octet-stream
|
||||
# path: "%kernel.project_dir%/public/uploads"
|
||||
# path_uri: "/uploads"
|
||||
# path_locked:
|
||||
# - "%kernel.project_dir%/public/uploads"
|
||||
|
|
|
@ -49,5 +49,6 @@ security:
|
|||
- { path: ^/admin/task, roles: ROLE_ADMIN }
|
||||
- { path: ^/admin/setting, roles: ROLE_ADMIN }
|
||||
- { path: ^/admin/site, roles: ROLE_WRITER }
|
||||
- { path: ^/admin/file_manager, roles: ROLE_WRITER }
|
||||
- { path: ^/admin, roles: ROLE_USER }
|
||||
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
|
|
232
core/Controller/FileManager/FileManagerAdminController.php
Normal file
232
core/Controller/FileManager/FileManagerAdminController.php
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\FileManager;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\FileManager\FsFileManager;
|
||||
use App\Core\Form\FileManager\DirectoryCreateType;
|
||||
use App\Core\Form\FileManager\DirectoryRenameType;
|
||||
use App\Core\Form\FileManager\FileUploadType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/file_manager")
|
||||
*/
|
||||
class FileManagerAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="admin_file_manager_index")
|
||||
*/
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->render('@Core/file_manager/index.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/directory", name="admin_file_manager_api_directory", options={"expose"=true})
|
||||
*/
|
||||
public function directory(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$files = $manager->list($request->query->get('directory', '/'));
|
||||
|
||||
return $this->json($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/info", name="admin_file_manager_info", options={"expose"=true})
|
||||
*/
|
||||
public function info(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$info = $manager->info($request->query->get('file'));
|
||||
|
||||
if (!$info) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$path = $manager->getPathUri().'/'.$info->getRelativePathname();
|
||||
|
||||
return $this->render('@Core/file_manager/info.html.twig', [
|
||||
'info' => $info,
|
||||
'path' => $path,
|
||||
'isLocked' => $manager->isLocked($info->getRelativePathname()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/directory/new", name="admin_file_manager_directory_new", options={"expose"=true}, methods={"GET", "POST"})
|
||||
*/
|
||||
public function directoryNew(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$info = $manager->info($request->query->get('file'));
|
||||
|
||||
if (!$info) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if (!$info->isDir()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/directory_new.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
$form = $this->createForm(DirectoryCreateType::class);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$status = $manager->createDirectory($form->get('name')->getData(), $request->query->get('file'));
|
||||
|
||||
if (true === $status) {
|
||||
$this->addFlash('success', 'Directory created.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'Directory not created.');
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('warning', 'Unauthorized char(s).');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $info->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/directory_new.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'locked' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/directory/rename", name="admin_file_manager_directory_rename", methods={"GET", "POST"})
|
||||
*/
|
||||
public function directoryRename(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$info = $manager->info($request->query->get('file'));
|
||||
|
||||
if (!$info) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if (!$info->isDir()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/directory_rename.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$form = $this->createForm(DirectoryRenameType::class);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$status = $manager->renameDirectory($form->get('name')->getData(), $request->query->get('file'));
|
||||
|
||||
if (true === $status) {
|
||||
$this->addFlash('success', 'Directory renamed.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'Directory not renamed.');
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('warning', 'Unauthorized char(s).');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $info->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/directory_rename.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'locked' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/upload", name="admin_file_manager_upload", options={"expose"=true}, methods={"GET", "POST"})
|
||||
*/
|
||||
public function upload(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$info = $manager->info($request->query->get('file'));
|
||||
|
||||
if (!$info) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if (!$info->isDir()) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/upload.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
$form = $this->createForm(FileUploadType::class, null, [
|
||||
'mimes' => $manager->getMimes(),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$manager->upload($form->get('files')->getData(), $request->query->get('file'));
|
||||
|
||||
$this->addFlash('success', 'Files uploaded.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'Unauthorized file type(s).');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $info->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/upload.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'locked' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/delete", name="admin_file_manager_delete", methods={"DELETE"})
|
||||
*/
|
||||
public function delete(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$path = $request->request->get('file');
|
||||
$info = $manager->info($request->request->get('file'));
|
||||
|
||||
if (!$info) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($this->isCsrfTokenValid('delete', $request->request->get('_token'))) {
|
||||
if ($manager->delete($path)) {
|
||||
$this->addFlash('success', 'The data has been removed.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The data has not been removed.');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $info->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'file_manager';
|
||||
}
|
||||
}
|
|
@ -9,6 +9,30 @@ class Configuration implements ConfigurationInterface
|
|||
{
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$defaultMimetypes = [
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'application/pdf',
|
||||
'application/ogg',
|
||||
'video/mp4',
|
||||
'application/zip',
|
||||
'multipart/x-zip',
|
||||
'application/rar',
|
||||
'application/x-rar-compressed',
|
||||
'application/x-zip-compressed',
|
||||
'application/tar',
|
||||
'application/x-tar',
|
||||
'text/plain',
|
||||
'text/x-asm',
|
||||
'application/octet-stream'
|
||||
];
|
||||
|
||||
$defaultLocked = [
|
||||
'%kernel.project_dir%/public/uploads',
|
||||
];
|
||||
|
||||
$treeBuilder = new TreeBuilder('core');
|
||||
|
||||
$treeBuilder->getRootNode()
|
||||
|
@ -16,10 +40,12 @@ class Configuration implements ConfigurationInterface
|
|||
->arrayNode('site')
|
||||
->children()
|
||||
->scalarNode('name')
|
||||
->defaultValue('Murph')
|
||||
->isRequired()
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->scalarNode('logo')
|
||||
->defaultValue('build/images/core/logo.svg')
|
||||
->isRequired()
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
|
@ -58,6 +84,29 @@ class Configuration implements ConfigurationInterface
|
|||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('file_manager')
|
||||
->children()
|
||||
->arrayNode('mimes')
|
||||
->scalarPrototype()
|
||||
->end()
|
||||
->defaultValue($defaultMimetypes)
|
||||
->end()
|
||||
->scalarNode('path')
|
||||
->defaultValue('%kernel.project_dir%/public/uploads')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->scalarNode('path_uri')
|
||||
->defaultValue('/uploads')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->arrayNode('path_locked')
|
||||
->scalarPrototype()
|
||||
->end()
|
||||
->defaultValue($defaultLocked)
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
|
|
230
core/FileManager/FsFileManager.php
Normal file
230
core/FileManager/FsFileManager.php
Normal file
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\FileManager;
|
||||
|
||||
use App\Core\Form\FileUploadHandler;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
/**
|
||||
* class FsFileManager.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class FsFileManager
|
||||
{
|
||||
protected array $mimes;
|
||||
protected string $path;
|
||||
protected string $pathUri;
|
||||
protected array $pathLocked;
|
||||
protected FileUploadHandler $uploadHandler;
|
||||
|
||||
public function __construct(ParameterBagInterface $params, FileUploadHandler $uploadHandler)
|
||||
{
|
||||
$config = $params->get('core')['file_manager'];
|
||||
|
||||
$this->uploadHandler = $uploadHandler;
|
||||
$this->mimes = $config['mimes'];
|
||||
$this->path = $config['path'];
|
||||
$this->pathUri = $this->normalizePath($config['path_uri']);
|
||||
|
||||
foreach ($config['path_locked'] as $k => $v) {
|
||||
$config['path_locked'][$k] = sprintf('/%s/', $this->normalizePath($v));
|
||||
}
|
||||
|
||||
$this->pathLocked = $config['path_locked'];
|
||||
}
|
||||
|
||||
public function list(string $directory): array
|
||||
{
|
||||
$directory = $this->normalizePath($directory);
|
||||
|
||||
$breadcrumb = ['/'];
|
||||
|
||||
if ($directory) {
|
||||
$breadcrumb = array_merge(
|
||||
$breadcrumb,
|
||||
explode('/', $directory)
|
||||
);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'breadcrumb' => $breadcrumb,
|
||||
'parent' => dirname($directory),
|
||||
'directories' => [],
|
||||
'files' => [],
|
||||
];
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->directories()->depth('== 0')->in($this->path.'/'.$directory);
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$data['directories'][] = [
|
||||
'basename' => $file->getBasename(),
|
||||
'path' => $directory.'/'.$file->getBasename(),
|
||||
'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
|
||||
'mime' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()->depth('== 0')->in($this->path.'/'.$directory);
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$data['files'][] = [
|
||||
'basename' => $file->getBasename(),
|
||||
'path' => $directory,
|
||||
'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
|
||||
'mime' => mime_content_type($file->getRealPath()),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function info(string $path): ?SplFileInfo
|
||||
{
|
||||
$path = $this->normalizePath($path);
|
||||
|
||||
if ('' === $path) {
|
||||
return new SplFileInfo($this->path, '', '');
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->in($this->path)
|
||||
->name(basename($path))
|
||||
;
|
||||
|
||||
$dirname = dirname($path);
|
||||
|
||||
if ('.' === $dirname) {
|
||||
$dirname = '';
|
||||
}
|
||||
|
||||
foreach ($finder as $file) {
|
||||
if ($file->getRelativePath() === $dirname) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function createDirectory(string $name, string $path): bool
|
||||
{
|
||||
$file = $this->info($path);
|
||||
|
||||
if (!$file || $this->isLocked($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
$path = $file->getPathname().'/'.$this->normalizePath($name);
|
||||
|
||||
if ($filesystem->exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem->mkdir($path, 0700);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renameDirectory(string $name, string $path): bool
|
||||
{
|
||||
$file = $this->info($path);
|
||||
|
||||
if (!$file || $this->isLocked($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
$newPath = $file->getPath().'/'.$this->normalizePath($name);
|
||||
|
||||
if ($filesystem->exists($newPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem->rename($file->getPathName(), $newPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function upload($files, string $path)
|
||||
{
|
||||
if ($files instanceof UploadedFile) {
|
||||
$files = [$files];
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->uploadHandler->handleForm($file, $this->path.'/'.$path, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $path): bool
|
||||
{
|
||||
$file = $this->info($path);
|
||||
|
||||
if ($this->isLocked($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$filesystem = new Filesystem();
|
||||
$filesystem->remove($file);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isLocked($path): bool
|
||||
{
|
||||
$file = $this->info($path);
|
||||
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->pathLocked as $lock) {
|
||||
if (u($file->getPathName().'/')->startsWith($lock)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getPathUri(): string
|
||||
{
|
||||
return $this->pathUri;
|
||||
}
|
||||
|
||||
public function getMimes(): array
|
||||
{
|
||||
return $this->mimes;
|
||||
}
|
||||
|
||||
public function getPathLocked(): array
|
||||
{
|
||||
return $this->pathLocked;
|
||||
}
|
||||
|
||||
protected function normalizePath(string $path): string
|
||||
{
|
||||
return (string) u($path)
|
||||
->replace('..', '.')
|
||||
->replaceMatches('#/{2,}#', '/')
|
||||
->replaceMatches('#^.$#', '')
|
||||
->trim('/')
|
||||
->trim()
|
||||
;
|
||||
}
|
||||
}
|
43
core/Form/FileManager/DirectoryCreateType.php
Normal file
43
core/Form/FileManager/DirectoryCreateType.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
use Symfony\Component\Validator\Constraints\All;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
class DirectoryCreateType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'name',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Name',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new Regex([
|
||||
'pattern' => '#['.preg_quote('\\/?%*:|"<>').'\t\n\r]+#',
|
||||
'match' => false,
|
||||
]),
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
]);
|
||||
}
|
||||
}
|
43
core/Form/FileManager/DirectoryRenameType.php
Normal file
43
core/Form/FileManager/DirectoryRenameType.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
use Symfony\Component\Validator\Constraints\All;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
class DirectoryRenameType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'name',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Name',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new Regex([
|
||||
'pattern' => '#['.preg_quote('\\/?%*:|"<>').'\t\n\r]+#',
|
||||
'match' => false,
|
||||
]),
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
]);
|
||||
}
|
||||
}
|
42
core/Form/FileManager/FileUploadType.php
Normal file
42
core/Form/FileManager/FileUploadType.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
use Symfony\Component\Validator\Constraints\All;
|
||||
|
||||
class FileUploadType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'files',
|
||||
FileType::class,
|
||||
[
|
||||
'label' => 'Files',
|
||||
'required' => true,
|
||||
'multiple' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new All([
|
||||
new File([
|
||||
'mimeTypes' => $options['mimes'],
|
||||
]),
|
||||
]),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'mimes' => [],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -11,18 +11,25 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|||
*/
|
||||
class FileUploadHandler
|
||||
{
|
||||
public function handleForm(?UploadedFile $uploadedFile, string $path, callable $afterUploadCallback): void
|
||||
public function handleForm(?UploadedFile $uploadedFile, string $path, ?callable $afterUploadCallback = null, bool $keepOriginalFilename = false): void
|
||||
{
|
||||
if (null === $uploadedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
|
||||
if ($keepOriginalFilename) {
|
||||
$filename = $originalFilename.'.'.$uploadedFile->guessExtension();
|
||||
} else {
|
||||
$safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
|
||||
$filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
|
||||
}
|
||||
|
||||
$uploadedFile->move($path, $filename);
|
||||
|
||||
if ($afterUploadCallback) {
|
||||
$afterUploadCallback($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"Error": "Erreur"
|
||||
"The data has been saved.": "Les données ont été sauvegardées."
|
||||
"The data has been removed.": "Les données ont été supprimées."
|
||||
"The data has not been removed.": "Les données n'ont pas été supprimées."
|
||||
"You must add a navigation.": "Vous devez ajouter une navigation."
|
||||
"E-mail sent.": "E-mail envoyé"
|
||||
"name": "Nom"
|
||||
|
@ -165,3 +166,22 @@
|
|||
"For selection": "Pour la sélection"
|
||||
"For all items": "Pour tous les éléments"
|
||||
"Run": "Lancer"
|
||||
"Download": "Télécharger"
|
||||
"Absolute URL": "URL absolue"
|
||||
"Relative URL": "URL relative"
|
||||
"Creation date": "Date de création"
|
||||
"Modification date": "Date de modification"
|
||||
"File size": "Poids du fichier"
|
||||
"File manager": "Gestionnaire de fichiers"
|
||||
"File(s) uploaded.": "Fichier(s) déposé(s)."
|
||||
"Unauthorized file type(s).": "Type(s) de fichier non autorisé(s)"
|
||||
"Directory locked.": "Répertoire verrouillé."
|
||||
"File locked.": "Fichier verrouillé."
|
||||
"Upload files": "Envoyer des fichiers"
|
||||
"Files": "Fichiers"
|
||||
"Directory created.": "Répertoire créé."
|
||||
"Directory not created.": "Répertoire non créé."
|
||||
"Directory renamed.": "Répertoire renommé."
|
||||
"Directory not renamed.": "Répertoire non renommé."
|
||||
"Rename": "Renommer"
|
||||
"Rename directory": "Renommer le répertoire"
|
||||
|
|
|
@ -47,6 +47,16 @@
|
|||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ macros_menu.active_class('file_manager', section) }}" href="{{ path('admin_file_manager_index') }}">
|
||||
<span class="fa fa-photo-video"></span>
|
||||
|
||||
<span class="nav-item-label">
|
||||
{{ 'Files'|trans }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
|
|
1
core/Resources/views/file_manager/_form.html.twig
Normal file
1
core/Resources/views/file_manager/_form.html.twig
Normal file
|
@ -0,0 +1 @@
|
|||
{{ form_widget(form) }}
|
33
core/Resources/views/file_manager/directory_new.html.twig
Normal file
33
core/Resources/views/file_manager/directory_new.html.twig
Normal file
|
@ -0,0 +1,33 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ 'New directory'|trans }}
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if locked %}
|
||||
<p class="text-danger text-center">
|
||||
<span class="d-block display-4 mb-3">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
|
||||
{{ 'Directory locked.'|trans }}
|
||||
</p>
|
||||
{% else %}
|
||||
<form action="{{ path('admin_file_manager_directory_new', {file: file}) }}" id="form-file-manager-directory" method="POST" enctype="multipart/form-data">
|
||||
{{ include('@Core/file_manager/_form.html.twig') }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
|
||||
{% if not locked %}
|
||||
<button type="submit" form="form-file-manager-directory" class="btn btn-primary">{{ 'Save'|trans }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
33
core/Resources/views/file_manager/directory_rename.html.twig
Normal file
33
core/Resources/views/file_manager/directory_rename.html.twig
Normal file
|
@ -0,0 +1,33 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ 'Rename directory'|trans }}
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if locked %}
|
||||
<p class="text-danger text-center">
|
||||
<span class="d-block display-4 mb-3">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
|
||||
{{ 'Directory locked.'|trans }}
|
||||
</p>
|
||||
{% else %}
|
||||
<form action="{{ path('admin_file_manager_directory_rename', {file: file}) }}" id="form-file-manager-directory" method="POST" enctype="multipart/form-data">
|
||||
{{ include('@Core/file_manager/_form.html.twig') }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
|
||||
{% if not locked %}
|
||||
<button type="submit" form="form-file-manager-directory" class="btn btn-primary">{{ 'Save'|trans }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
16
core/Resources/views/file_manager/index.html.twig
Normal file
16
core/Resources/views/file_manager/index.html.twig
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends '@Core/admin/layout.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="bg-light pl-5 pr-4 pt-5 pb-5">
|
||||
<div class="d-flex">
|
||||
<div class="mr-auto w-50">
|
||||
<h1 class="display-5">
|
||||
{{ 'File manager'|trans }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="file-manager">
|
||||
</div>
|
||||
{% endblock %}
|
93
core/Resources/views/file_manager/info.html.twig
Normal file
93
core/Resources/views/file_manager/info.html.twig
Normal file
|
@ -0,0 +1,93 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ 'Information'|trans }}
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if info.type == 'file' %}
|
||||
<div class="form-group">
|
||||
<label for="file-manager-url">{{ 'Absolute URL'|trans }}</label><br>
|
||||
<input class="form-control" type="text" readonly value="{{ absolute_url(asset(path)) }}" id="file-manager-url" />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<label for="file-manager-url2">{{ 'Relative URL'|trans }}</label><br>
|
||||
<input class="form-control" type="text" readonly value="{{ asset(path) }}" id="file-manager-url2" />
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="list-group mb-3">
|
||||
{% if info.type == 'file' %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ 'File size'|trans }}
|
||||
|
||||
<button class="btn btn-sm btn-light">{{ info.size|readable_filesize }}</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ 'Creation date'|trans }}
|
||||
|
||||
<button class="btn btn-sm btn-light">{{ info.mTime|date('Y-m-d H:i') }}</button>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{ 'Modification date'|trans }}
|
||||
|
||||
<button class="btn btn-sm btn-light">{{ info.cTime|date('Y-m-d H:i') }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if info.extension in ['jpeg', 'jpg', 'gif', 'png', 'svg'] %}
|
||||
<div class="card">
|
||||
<div class="card-img-top bg-tiles text-center">
|
||||
<a href="{{ asset(path) }}" target="_blank">
|
||||
<img src="{{ asset(path) }}" class="img-fluid">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
{% if not isLocked %}
|
||||
<div>
|
||||
<button type="submit" form="form-file-delete" class="btn btn-danger" form="form-file-delete">
|
||||
{{ 'Delete'|trans }}
|
||||
</button>
|
||||
|
||||
{% if info.isDir %}
|
||||
<button form="form-file-delete" class="btn btn-primary" data-modal="{{ path('admin_file_manager_directory_rename', {file: info.relativePathname}) }}">
|
||||
{{ 'Rename'|trans }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="btn btn-light">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="div">
|
||||
{% if info.type == 'file' %}
|
||||
<a class="btn btn-primary" href="{{ asset(path) }}" target="_blank">
|
||||
{{ 'Download'|trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Close'|trans }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not isLocked %}
|
||||
<form method="post" action="{{ path('admin_file_manager_delete') }}" id="form-file-delete" data-form-confirm>
|
||||
<input type="hidden" name="file" value="{{ info.relativePathname }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('delete') }}">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
</form>
|
||||
{% endif %}
|
33
core/Resources/views/file_manager/upload.html.twig
Normal file
33
core/Resources/views/file_manager/upload.html.twig
Normal file
|
@ -0,0 +1,33 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ 'Upload files'|trans }}
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if locked %}
|
||||
<p class="text-danger text-center">
|
||||
<span class="d-block display-4 mb-3">
|
||||
<span class="fa fa-lock"></span>
|
||||
</span>
|
||||
|
||||
{{ 'Directory locked.'|trans }}
|
||||
</p>
|
||||
{% else %}
|
||||
<form action="{{ path('admin_file_manager_upload', {file: file}) }}" id="form-file-manager-upload" method="POST" enctype="multipart/form-data">
|
||||
{{ include('@Core/file_manager/_form.html.twig') }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
|
||||
{% if not locked %}
|
||||
<button type="submit" form="form-file-manager-upload" class="btn btn-primary">{{ 'Upload'|trans }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div class="p-2 text-center">
|
||||
<a class="btn btn-primary" href="{{ asset(value.pathname) }}" target="_blank">
|
||||
Télécharger
|
||||
{{ 'Download'|trans }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
"devDependencies": {
|
||||
"@symfony/stimulus-bridge": "^2.0.0",
|
||||
"@symfony/webpack-encore": "^1.0.0",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
|
||||
"@vue/babel-preset-jsx": "^1.2.4",
|
||||
"core-js": "^3.0.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"sass-loader": "^11.0.0",
|
||||
"stimulus": "^2.0.0",
|
||||
"vue-loader": "^15.9.5",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack-notifier": "^1.6.0"
|
||||
},
|
||||
"license": "UNLICENSED",
|
||||
|
@ -20,6 +24,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap": "^4.3.1",
|
||||
"choices.js": "^9.0.1",
|
||||
"flag-icon-css": "^3.5.0",
|
||||
|
@ -29,6 +34,7 @@
|
|||
"sortablejs": "^1.13.0",
|
||||
"tinymce": "^5.7.1",
|
||||
"vanillajs-datepicker": "^1.1.2",
|
||||
"vue": "^2.6.14",
|
||||
"zxcvbn": "^4.4.2"
|
||||
}
|
||||
}
|
||||
|
|
18
symfony.lock
18
symfony.lock
|
@ -108,6 +108,18 @@
|
|||
"friendsofphp/proxy-manager-lts": {
|
||||
"version": "v1.0.3"
|
||||
},
|
||||
"friendsofsymfony/jsrouting-bundle": {
|
||||
"version": "2.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "master",
|
||||
"version": "2.3",
|
||||
"ref": "a9f2e49180f75cdc71ae279a929c4b2e0638de84"
|
||||
},
|
||||
"files": [
|
||||
"config/routes/fos_js_routing.yaml"
|
||||
]
|
||||
},
|
||||
"gedmo/doctrine-extensions": {
|
||||
"version": "v3.0.3"
|
||||
},
|
||||
|
@ -220,6 +232,9 @@
|
|||
"config/packages/ansi_to_html.yaml"
|
||||
]
|
||||
},
|
||||
"spe/filesize-extension-bundle": {
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"spomky-labs/otphp": {
|
||||
"version": "v10.0.1"
|
||||
},
|
||||
|
@ -667,5 +682,8 @@
|
|||
},
|
||||
"webmozart/assert": {
|
||||
"version": "1.10.0"
|
||||
},
|
||||
"willdurand/jsonp-callback-validator": {
|
||||
"version": "v1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ Encore
|
|||
*/
|
||||
.cleanupOutputBeforeBuild()
|
||||
.enableBuildNotifications()
|
||||
.enableVueLoader()
|
||||
.enableSourceMaps(!Encore.isProduction())
|
||||
// enables hashed filenames (e.g. app.abc123.css)
|
||||
.enableVersioning(Encore.isProduction())
|
||||
|
|
239
yarn.lock
239
yarn.lock
|
@ -141,6 +141,13 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.13.0"
|
||||
|
||||
"@babel/helper-module-imports@^7.0.0":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3"
|
||||
integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.14.5"
|
||||
|
||||
"@babel/helper-module-imports@^7.12.13":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0"
|
||||
|
@ -175,6 +182,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af"
|
||||
integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==
|
||||
|
||||
"@babel/helper-plugin-utils@^7.14.5":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
|
||||
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
|
||||
|
||||
"@babel/helper-remap-async-to-generator@^7.13.0":
|
||||
version "7.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209"
|
||||
|
@ -220,6 +232,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
|
||||
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.14.5":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8"
|
||||
integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==
|
||||
|
||||
"@babel/helper-validator-option@^7.12.17":
|
||||
version "7.12.17"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831"
|
||||
|
@ -402,6 +419,13 @@
|
|||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.8.0"
|
||||
|
||||
"@babel/plugin-syntax-jsx@^7.2.0":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201"
|
||||
integrity sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.14.5"
|
||||
|
||||
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
|
||||
version "7.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
|
||||
|
@ -824,6 +848,14 @@
|
|||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.14.5":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff"
|
||||
integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.14.5"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@discoveryjs/json-ext@^0.5.0":
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752"
|
||||
|
@ -1028,6 +1060,101 @@
|
|||
"@types/webpack-sources" "*"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@vue/babel-helper-vue-jsx-merge-props@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81"
|
||||
integrity sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==
|
||||
|
||||
"@vue/babel-plugin-transform-vue-jsx@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.2.1.tgz#646046c652c2f0242727f34519d917b064041ed7"
|
||||
integrity sha512-HJuqwACYehQwh1fNT8f4kyzqlNMpBuUK4rSiSES5D4QsYncv5fxFsLyrxFPG2ksO7t5WP+Vgix6tt6yKClwPzA==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.0.0"
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
"@vue/babel-helper-vue-jsx-merge-props" "^1.2.1"
|
||||
html-tags "^2.0.0"
|
||||
lodash.kebabcase "^4.1.1"
|
||||
svg-tags "^1.0.0"
|
||||
|
||||
"@vue/babel-preset-jsx@^1.2.4":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.2.4.tgz#92fea79db6f13b01e80d3a0099e2924bdcbe4e87"
|
||||
integrity sha512-oRVnmN2a77bYDJzeGSt92AuHXbkIxbf/XXSE3klINnh9AXBmVS1DGa1f0d+dDYpLfsAKElMnqKTQfKn7obcL4w==
|
||||
dependencies:
|
||||
"@vue/babel-helper-vue-jsx-merge-props" "^1.2.1"
|
||||
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
|
||||
"@vue/babel-sugar-composition-api-inject-h" "^1.2.1"
|
||||
"@vue/babel-sugar-composition-api-render-instance" "^1.2.4"
|
||||
"@vue/babel-sugar-functional-vue" "^1.2.2"
|
||||
"@vue/babel-sugar-inject-h" "^1.2.2"
|
||||
"@vue/babel-sugar-v-model" "^1.2.3"
|
||||
"@vue/babel-sugar-v-on" "^1.2.3"
|
||||
|
||||
"@vue/babel-sugar-composition-api-inject-h@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.2.1.tgz#05d6e0c432710e37582b2be9a6049b689b6f03eb"
|
||||
integrity sha512-4B3L5Z2G+7s+9Bwbf+zPIifkFNcKth7fQwekVbnOA3cr3Pq71q71goWr97sk4/yyzH8phfe5ODVzEjX7HU7ItQ==
|
||||
dependencies:
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
|
||||
"@vue/babel-sugar-composition-api-render-instance@^1.2.4":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.2.4.tgz#e4cbc6997c344fac271785ad7a29325c51d68d19"
|
||||
integrity sha512-joha4PZznQMsxQYXtR3MnTgCASC9u3zt9KfBxIeuI5g2gscpTsSKRDzWQt4aqNIpx6cv8On7/m6zmmovlNsG7Q==
|
||||
dependencies:
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
|
||||
"@vue/babel-sugar-functional-vue@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.2.2.tgz#267a9ac8d787c96edbf03ce3f392c49da9bd2658"
|
||||
integrity sha512-JvbgGn1bjCLByIAU1VOoepHQ1vFsroSA/QkzdiSs657V79q6OwEWLCQtQnEXD/rLTA8rRit4rMOhFpbjRFm82w==
|
||||
dependencies:
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
|
||||
"@vue/babel-sugar-inject-h@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.2.2.tgz#d738d3c893367ec8491dcbb669b000919293e3aa"
|
||||
integrity sha512-y8vTo00oRkzQTgufeotjCLPAvlhnpSkcHFEp60+LJUwygGcd5Chrpn5480AQp/thrxVm8m2ifAk0LyFel9oCnw==
|
||||
dependencies:
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
|
||||
"@vue/babel-sugar-v-model@^1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.2.3.tgz#fa1f29ba51ebf0aa1a6c35fa66d539bc459a18f2"
|
||||
integrity sha512-A2jxx87mySr/ulAsSSyYE8un6SIH0NWHiLaCWpodPCVOlQVODCaSpiR4+IMsmBr73haG+oeCuSvMOM+ttWUqRQ==
|
||||
dependencies:
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
"@vue/babel-helper-vue-jsx-merge-props" "^1.2.1"
|
||||
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
|
||||
camelcase "^5.0.0"
|
||||
html-tags "^2.0.0"
|
||||
svg-tags "^1.0.0"
|
||||
|
||||
"@vue/babel-sugar-v-on@^1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.2.3.tgz#342367178586a69f392f04bfba32021d02913ada"
|
||||
integrity sha512-kt12VJdz/37D3N3eglBywV8GStKNUhNrsxChXIV+o0MwVXORYuhDTHJRKPgLJRb/EY3vM2aRFQdxJBp9CLikjw==
|
||||
dependencies:
|
||||
"@babel/plugin-syntax-jsx" "^7.2.0"
|
||||
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
|
||||
camelcase "^5.0.0"
|
||||
|
||||
"@vue/component-compiler-utils@^3.1.0":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.1.tgz#c3e3cb85ea80cc157eeaffe128a25dfe77e2f326"
|
||||
integrity sha512-Mci9WJYLRjyJEBkGHMPxZ1ihJ9l6gOy2Gr6hpYZUNpQoe5+nbpeb3w00aP+PSHJygCF+fxJsqp7Af1zGDITzuw==
|
||||
dependencies:
|
||||
consolidate "^0.15.1"
|
||||
hash-sum "^1.0.2"
|
||||
lru-cache "^4.1.2"
|
||||
merge-source-map "^1.1.0"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
source-map "~0.6.1"
|
||||
vue-template-es2015-compiler "^1.9.0"
|
||||
optionalDependencies:
|
||||
prettier "^1.18.2"
|
||||
|
||||
"@webassemblyjs/ast@1.11.0":
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f"
|
||||
|
@ -1414,6 +1541,13 @@ aws4@^1.8.0:
|
|||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||
|
||||
axios@^0.21.1:
|
||||
version "0.21.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
||||
dependencies:
|
||||
follow-redirects "^1.10.0"
|
||||
|
||||
babel-loader@^8.0.0:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81"
|
||||
|
@ -1489,6 +1623,11 @@ block-stream@*:
|
|||
dependencies:
|
||||
inherits "~2.0.0"
|
||||
|
||||
bluebird@^3.1.1:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||
|
||||
body-parser@1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
|
@ -1884,6 +2023,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
|
||||
|
||||
consolidate@^0.15.1:
|
||||
version "0.15.1"
|
||||
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
|
||||
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
|
||||
dependencies:
|
||||
bluebird "^3.1.1"
|
||||
|
||||
content-disposition@0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||
|
@ -2157,6 +2303,11 @@ dashdash@^1.12.0:
|
|||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
|
@ -2757,6 +2908,11 @@ follow-redirects@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
|
||||
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
|
||||
|
||||
follow-redirects@^1.10.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
|
||||
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
|
||||
|
||||
foreach@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
||||
|
@ -3023,6 +3179,16 @@ has@^1.0.0, has@^1.0.3:
|
|||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hash-sum@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
|
||||
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
|
||||
|
||||
he@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hex-color-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||
|
@ -3063,6 +3229,11 @@ html-entities@^1.3.1:
|
|||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
|
||||
integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
|
||||
|
||||
html-tags@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
|
||||
integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
|
||||
|
||||
htmlparser2@^3.10.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
||||
|
@ -3662,7 +3833,7 @@ loader-utils@1.2.3:
|
|||
emojis-list "^2.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^1.4.0:
|
||||
loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
||||
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
|
||||
|
@ -3700,6 +3871,11 @@ lodash.debounce@^4.0.8:
|
|||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.kebabcase@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
||||
integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY=
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
|
@ -3730,7 +3906,7 @@ loud-rejection@^1.0.0:
|
|||
currently-unhandled "^0.4.1"
|
||||
signal-exit "^3.0.0"
|
||||
|
||||
lru-cache@^4.0.1:
|
||||
lru-cache@^4.0.1, lru-cache@^4.1.2:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
|
||||
|
@ -3815,6 +3991,13 @@ merge-descriptors@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
merge-source-map@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
|
||||
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
|
@ -4781,6 +4964,11 @@ postcss@^8.2.8:
|
|||
nanoid "^3.1.20"
|
||||
source-map "^0.6.1"
|
||||
|
||||
prettier@^1.18.2:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
|
||||
pretty-error@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-3.0.3.tgz#57f4b16aebcadbd681db2c002ae0f95bf87b24cf"
|
||||
|
@ -5710,6 +5898,11 @@ supports-color@^7.0.0, supports-color@^7.1.0:
|
|||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
svg-tags@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
|
||||
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
|
||||
|
||||
svgo@^1.0.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
|
||||
|
@ -6054,6 +6247,48 @@ verror@1.10.0:
|
|||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vue-hot-reload-api@^2.3.0:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
|
||||
|
||||
vue-loader@^15.9.5:
|
||||
version "15.9.7"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.7.tgz#15b05775c3e0c38407679393c2ce6df673b01044"
|
||||
integrity sha512-qzlsbLV1HKEMf19IqCJqdNvFJRCI58WNbS6XbPqK13MrLz65es75w392MSQ5TsARAfIjUw+ATm3vlCXUJSOH9Q==
|
||||
dependencies:
|
||||
"@vue/component-compiler-utils" "^3.1.0"
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.1.0"
|
||||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-style-loader@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
|
||||
integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
|
||||
dependencies:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-template-compiler@^2.6.14:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763"
|
||||
integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue@^2.6.14:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
|
||||
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
|
||||
|
||||
watchpack@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"
|
||||
|
|
Loading…
Reference in a new issue