]> BookStack Code Mirror - bookstack/commitdiff
Started work on attachments
authorDan Brown <redacted>
Sun, 9 Oct 2016 17:58:22 +0000 (18:58 +0100)
committerDan Brown <redacted>
Sun, 9 Oct 2016 17:58:22 +0000 (18:58 +0100)
Created base models and started user-facing controls.

15 files changed:
app/Exceptions/FileUploadException.php [new file with mode: 0644]
app/File.php [new file with mode: 0644]
app/Http/Controllers/FileController.php [new file with mode: 0644]
app/Page.php
app/Repos/PageRepo.php
app/Services/FileService.php [new file with mode: 0644]
app/Services/ImageService.php
app/Services/UploadService.php [new file with mode: 0644]
config/filesystems.php
database/migrations/2016_10_09_142037_create_files_table.php [new file with mode: 0644]
resources/assets/js/controllers.js
resources/assets/sass/_components.scss
resources/views/pages/form-toolbox.blade.php
routes/web.php
storage/uploads/files/.gitignore [new file with mode: 0755]

diff --git a/app/Exceptions/FileUploadException.php b/app/Exceptions/FileUploadException.php
new file mode 100644 (file)
index 0000000..af97607
--- /dev/null
@@ -0,0 +1,4 @@
+<?php namespace BookStack\Exceptions;
+
+
+class FileUploadException extends PrettyException {}
\ No newline at end of file
diff --git a/app/File.php b/app/File.php
new file mode 100644 (file)
index 0000000..ebfa0a2
--- /dev/null
@@ -0,0 +1,18 @@
+<?php namespace BookStack;
+
+
+class File extends Ownable
+{
+    protected $fillable = ['name', 'order'];
+
+    /**
+     * Get the page this file was uploaded to.
+     * @return mixed
+     */
+    public function page()
+    {
+        return $this->belongsTo(Page::class, 'uploaded_to');
+    }
+
+
+}
diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php
new file mode 100644 (file)
index 0000000..b97112c
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use BookStack\Exceptions\FileUploadException;
+use BookStack\File;
+use BookStack\Page;
+use BookStack\Repos\PageRepo;
+use BookStack\Services\FileService;
+use Illuminate\Http\Request;
+
+use BookStack\Http\Requests;
+
+class FileController extends Controller
+{
+    protected $fileService;
+    protected $file;
+    protected $pageRepo;
+
+    /**
+     * FileController constructor.
+     * @param FileService $fileService
+     * @param File $file
+     * @param PageRepo $pageRepo
+     */
+    public function __construct(FileService $fileService, File $file, PageRepo $pageRepo)
+    {
+        $this->fileService = $fileService;
+        $this->file = $file;
+        $this->pageRepo = $pageRepo;
+    }
+
+
+    /**
+     * Endpoint at which files are uploaded to.
+     * @param Request $request
+     */
+    public function upload(Request $request)
+    {
+        // TODO - Add file upload permission check
+        // TODO - ensure user has permission to edit relevant page.
+        // TODO - ensure uploads are deleted on page delete.
+
+        $this->validate($request, [
+            'uploaded_to' => 'required|integer|exists:pages,id'
+        ]);
+
+        $uploadedFile = $request->file('file');
+        $pageId = $request->get('uploaded_to');
+
+        try {
+            $file = $this->fileService->saveNewUpload($uploadedFile, $pageId);
+        } catch (FileUploadException $e) {
+            return response($e->getMessage(), 500);
+        }
+
+        return response()->json($file);
+    }
+
+    /**
+     * Get the files for a specific page.
+     * @param $pageId
+     * @return mixed
+     */
+    public function getFilesForPage($pageId)
+    {
+        // TODO - check view permission on page?
+        $page = $this->pageRepo->getById($pageId);
+        return response()->json($page->files);
+    }
+
+    /**
+     * Update the file sorting.
+     * @param $pageId
+     * @param Request $request
+     * @return mixed
+     */
+    public function sortFilesForPage($pageId, Request $request)
+    {
+        $this->validate($request, [
+            'files' => 'required|array',
+            'files.*.id' => 'required|integer',
+        ]);
+        $page = $this->pageRepo->getById($pageId);
+        $files = $request->get('files');
+        $this->fileService->updateFileOrderWithinPage($files, $pageId);
+        return response()->json(['message' => 'File order updated']);
+    }
+
+
+}
index 1961a4f7f6bc2ec0e66824397df51c2ec8652f8e..94bcce2b590d104a905b0da0be36e934d8156258 100644 (file)
@@ -54,6 +54,15 @@ class Page extends Entity
         return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc');
     }
 
         return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc');
     }
 
+    /**
+     * Get the files attached to this page.
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function files()
+    {
+        return $this->hasMany(File::class, 'uploaded_to')->orderBy('order', 'asc');
+    }
+
     /**
      * Get the url for this page.
      * @param string|bool $path
     /**
      * Get the url for this page.
      * @param string|bool $path
index 537dd9bd0a9fd63b33983b20e696963da759aac6..dc7bdb40304ec674f27fac3228f7b6226aba2f62 100644 (file)
@@ -48,7 +48,7 @@ class PageRepo extends EntityRepo
      * Get a page via a specific ID.
      * @param $id
      * @param bool $allowDrafts
      * Get a page via a specific ID.
      * @param $id
      * @param bool $allowDrafts
-     * @return mixed
+     * @return Page
      */
     public function getById($id, $allowDrafts = false)
     {
      */
     public function getById($id, $allowDrafts = false)
     {
diff --git a/app/Services/FileService.php b/app/Services/FileService.php
new file mode 100644 (file)
index 0000000..689fa06
--- /dev/null
@@ -0,0 +1,79 @@
+<?php namespace BookStack\Services;
+
+
+use BookStack\Exceptions\FileUploadException;
+use BookStack\File;
+use Exception;
+use Illuminate\Support\Collection;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+class FileService extends UploadService
+{
+
+    /**
+     * Store a new file upon user upload.
+     * @param UploadedFile $uploadedFile
+     * @param int $page_id
+     * @return File
+     * @throws FileUploadException
+     */
+    public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
+    {
+        $fileName = $uploadedFile->getClientOriginalName();
+        $fileData = file_get_contents($uploadedFile->getRealPath());
+
+        $storage = $this->getStorage();
+        $fileBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
+        $storageBasePath = $this->getStorageBasePath() . $fileBasePath;
+
+        $uploadFileName = $fileName;
+        while ($storage->exists($storageBasePath . $uploadFileName)) {
+            $uploadFileName = str_random(3) . $uploadFileName;
+        }
+
+        $filePath = $fileBasePath . $uploadFileName;
+        $fileStoragePath = $this->getStorageBasePath() . $filePath;
+
+        try {
+            $storage->put($fileStoragePath, $fileData);
+        } catch (Exception $e) {
+            throw new FileUploadException('File path ' . $fileStoragePath . ' could not be uploaded to. Ensure it is writable to the server.');
+        }
+
+        $largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
+
+        $file = File::forceCreate([
+            'name' => $fileName,
+            'path' => $filePath,
+            'uploaded_to' => $page_id,
+            'created_by' => user()->id,
+            'updated_by' => user()->id,
+            'order' => $largestExistingOrder + 1
+        ]);
+
+        return $file;
+    }
+
+    /**
+     * Get the file storage base path, amended for storage type.
+     * This allows us to keep a generic path in the database.
+     * @return string
+     */
+    private function getStorageBasePath()
+    {
+        return $this->isLocal() ? 'storage/' : '';
+    }
+
+    /**
+     * Updates the file ordering for a listing of attached files.
+     * @param array $fileList
+     * @param $pageId
+     */
+    public function updateFileOrderWithinPage($fileList, $pageId)
+    {
+        foreach ($fileList as $index => $file) {
+            File::where('uploaded_to', '=', $pageId)->where('id', '=', $file['id'])->update(['order' => $index]);
+        }
+    }
+
+}
\ No newline at end of file
index a56626246cbe634e5bcf6cb5dc3f76c7c50f2ad0..dfe2cf453705e07a7b311b73b0f2bffaa28bfb6a 100644 (file)
@@ -9,20 +9,13 @@ use Intervention\Image\ImageManager;
 use Illuminate\Contracts\Filesystem\Factory as FileSystem;
 use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
 use Illuminate\Contracts\Cache\Repository as Cache;
 use Illuminate\Contracts\Filesystem\Factory as FileSystem;
 use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
 use Illuminate\Contracts\Cache\Repository as Cache;
-use Setting;
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 
 use Symfony\Component\HttpFoundation\File\UploadedFile;
 
-class ImageService
+class ImageService extends UploadService
 {
 
     protected $imageTool;
 {
 
     protected $imageTool;
-    protected $fileSystem;
     protected $cache;
     protected $cache;
-
-    /**
-     * @var FileSystemInstance
-     */
-    protected $storageInstance;
     protected $storageUrl;
 
     /**
     protected $storageUrl;
 
     /**
@@ -34,8 +27,8 @@ class ImageService
     public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
     {
         $this->imageTool = $imageTool;
     public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
     {
         $this->imageTool = $imageTool;
-        $this->fileSystem = $fileSystem;
         $this->cache = $cache;
         $this->cache = $cache;
+        parent::__construct($fileSystem);
     }
 
     /**
     }
 
     /**
@@ -88,6 +81,9 @@ class ImageService
         if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
 
         $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
         if ($secureUploads) $imageName = str_random(16) . '-' . $imageName;
 
         $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
+
+        if ($this->isLocal()) $imagePath = '/public' . $imagePath;
+
         while ($storage->exists($imagePath . $imageName)) {
             $imageName = str_random(3) . $imageName;
         }
         while ($storage->exists($imagePath . $imageName)) {
             $imageName = str_random(3) . $imageName;
         }
@@ -100,6 +96,8 @@ class ImageService
             throw new ImageUploadException('Image Path ' . $fullPath . ' is not writable by the server.');
         }
 
             throw new ImageUploadException('Image Path ' . $fullPath . ' is not writable by the server.');
         }
 
+        if ($this->isLocal()) $fullPath = str_replace_first('/public', '', $fullPath);
+
         $imageDetails = [
             'name'       => $imageName,
             'path'       => $fullPath,
         $imageDetails = [
             'name'       => $imageName,
             'path'       => $fullPath,
@@ -119,6 +117,16 @@ class ImageService
         return $image;
     }
 
         return $image;
     }
 
+    /**
+     * Get the storage path, Dependant of storage type.
+     * @param Image $image
+     * @return mixed|string
+     */
+    protected function getPath(Image $image)
+    {
+        return ($this->isLocal()) ? ('public/' . $image->path) : $image->path;
+    }
+
     /**
      * Get the thumbnail for an image.
      * If $keepRatio is true only the width will be used.
     /**
      * Get the thumbnail for an image.
      * If $keepRatio is true only the width will be used.
@@ -135,7 +143,8 @@ class ImageService
     public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
     {
         $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
     public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
     {
         $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
-        $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path);
+        $imagePath = $this->getPath($image);
+        $thumbFilePath = dirname($imagePath) . $thumbDirName . basename($imagePath);
 
         if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
             return $this->getPublicUrl($thumbFilePath);
 
         if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
             return $this->getPublicUrl($thumbFilePath);
@@ -148,7 +157,7 @@ class ImageService
         }
 
         try {
         }
 
         try {
-            $thumb = $this->imageTool->make($storage->get($image->path));
+            $thumb = $this->imageTool->make($storage->get($imagePath));
         } catch (Exception $e) {
             if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
                 throw new ImageUploadException('The server cannot create thumbnails. Please check you have the GD PHP extension installed.');
         } catch (Exception $e) {
             if ($e instanceof \ErrorException || $e instanceof NotSupportedException) {
                 throw new ImageUploadException('The server cannot create thumbnails. Please check you have the GD PHP extension installed.');
@@ -183,8 +192,8 @@ class ImageService
     {
         $storage = $this->getStorage();
 
     {
         $storage = $this->getStorage();
 
-        $imageFolder = dirname($image->path);
-        $imageFileName = basename($image->path);
+        $imageFolder = dirname($this->getPath($image));
+        $imageFileName = basename($this->getPath($image));
         $allImages = collect($storage->allFiles($imageFolder));
 
         $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
         $allImages = collect($storage->allFiles($imageFolder));
 
         $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
@@ -222,35 +231,9 @@ class ImageService
         return $image;
     }
 
         return $image;
     }
 
-    /**
-     * Get the storage that will be used for storing images.
-     * @return FileSystemInstance
-     */
-    private function getStorage()
-    {
-        if ($this->storageInstance !== null) return $this->storageInstance;
-
-        $storageType = config('filesystems.default');
-        $this->storageInstance = $this->fileSystem->disk($storageType);
-
-        return $this->storageInstance;
-    }
-
-    /**
-     * Check whether or not a folder is empty.
-     * @param $path
-     * @return int
-     */
-    private function isFolderEmpty($path)
-    {
-        $files = $this->getStorage()->files($path);
-        $folders = $this->getStorage()->directories($path);
-        return count($files) === 0 && count($folders) === 0;
-    }
-
     /**
      * Gets a public facing url for an image by checking relevant environment variables.
     /**
      * Gets a public facing url for an image by checking relevant environment variables.
-     * @param $filePath
+     * @param string $filePath
      * @return string
      */
     private function getPublicUrl($filePath)
      * @return string
      */
     private function getPublicUrl($filePath)
@@ -273,6 +256,8 @@ class ImageService
             $this->storageUrl = $storageUrl;
         }
 
             $this->storageUrl = $storageUrl;
         }
 
+        if ($this->isLocal()) $filePath = str_replace_first('public/', '', $filePath);
+
         return ($this->storageUrl == false ? rtrim(baseUrl(''), '/') : rtrim($this->storageUrl, '/')) . $filePath;
     }
 
         return ($this->storageUrl == false ? rtrim(baseUrl(''), '/') : rtrim($this->storageUrl, '/')) . $filePath;
     }
 
diff --git a/app/Services/UploadService.php b/app/Services/UploadService.php
new file mode 100644 (file)
index 0000000..44d4bb4
--- /dev/null
@@ -0,0 +1,64 @@
+<?php namespace BookStack\Services;
+
+use Illuminate\Contracts\Filesystem\Factory as FileSystem;
+use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
+
+class UploadService
+{
+
+    /**
+     * @var FileSystem
+     */
+    protected $fileSystem;
+
+    /**
+     * @var FileSystemInstance
+     */
+    protected $storageInstance;
+
+
+    /**
+     * FileService constructor.
+     * @param $fileSystem
+     */
+    public function __construct(FileSystem $fileSystem)
+    {
+        $this->fileSystem = $fileSystem;
+    }
+
+    /**
+     * Get the storage that will be used for storing images.
+     * @return FileSystemInstance
+     */
+    protected function getStorage()
+    {
+        if ($this->storageInstance !== null) return $this->storageInstance;
+
+        $storageType = config('filesystems.default');
+        $this->storageInstance = $this->fileSystem->disk($storageType);
+
+        return $this->storageInstance;
+    }
+
+
+    /**
+     * Check whether or not a folder is empty.
+     * @param $path
+     * @return bool
+     */
+    protected function isFolderEmpty($path)
+    {
+        $files = $this->getStorage()->files($path);
+        $folders = $this->getStorage()->directories($path);
+        return (count($files) === 0 && count($folders) === 0);
+    }
+
+    /**
+     * Check if using a local filesystem.
+     * @return bool
+     */
+    protected function isLocal()
+    {
+        return strtolower(config('filesystems.default')) === 'local';
+    }
+}
\ No newline at end of file
index dbcb03db12d4d3b13d941529370bf729ad412b20..836f68d3d4224ece88983a5c4d4b7b859bc45b50 100644 (file)
@@ -56,7 +56,7 @@ return [
 
         'local' => [
             'driver' => 'local',
 
         'local' => [
             'driver' => 'local',
-            'root'   => public_path(),
+            'root'   => base_path(),
         ],
 
         'ftp' => [
         ],
 
         'ftp' => [
diff --git a/database/migrations/2016_10_09_142037_create_files_table.php b/database/migrations/2016_10_09_142037_create_files_table.php
new file mode 100644 (file)
index 0000000..4eaa86a
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateFilesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('files', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('name');
+            $table->string('path');
+            $table->integer('uploaded_to');
+
+            $table->boolean('external');
+            $table->integer('order');
+
+            $table->integer('created_by');
+            $table->integer('updated_by');
+
+            $table->index('uploaded_to');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('files');
+    }
+}
index a64bdfa8c6a7bc766b106f2cb90fab6daa45a2a8..52477a4ad3f7fd18ed6f16cc5502662e7274db9b 100644 (file)
@@ -460,7 +460,7 @@ module.exports = function (ngApp, events) {
              * Get all tags for the current book and add into scope.
              */
             function getTags() {
              * Get all tags for the current book and add into scope.
              */
             function getTags() {
-                let url = window.baseUrl('/ajax/tags/get/page/' + pageId);
+                let url = window.baseUrl(`/ajax/tags/get/page/${pageId}`);
                 $http.get(url).then((responseData) => {
                     $scope.tags = responseData.data;
                     addEmptyTag();
                 $http.get(url).then((responseData) => {
                     $scope.tags = responseData.data;
                     addEmptyTag();
@@ -529,6 +529,74 @@ 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 = [];
+
+            // 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(`/files/sort/page/${pageId}`, {files: $scope.files}).then(resp => {
+                    events.emit('success', resp.data.message);
+                });
+            }
+
+            /**
+             * Used by dropzone to get the endpoint to upload to.
+             * @returns {string}
+             */
+            $scope.getUploadUrl = function () {
+                return window.baseUrl('/files/upload');
+            };
+
+            /**
+             * Get files for the current page from the server.
+             */
+            function getFiles() {
+                let url = window.baseUrl(`/files/get/page/${pageId}`)
+                $http.get(url).then(responseData => {
+                    $scope.files = responseData.data;
+                    currentOrder = responseData.data.map(file => {return file.id}).join(':');
+                });
+            }
+            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.unshift(data);
+                });
+                events.emit('success', 'File uploaded');
+            };
+
+        }]);
+
 };
 
 
 };
 
 
index ccb69b44e3f91ddef14344a8290678b504cb51d3..7de42d43c6074bacb3d06f1a874dbc11d9ae61cf 100644 (file)
   }
 }
 
   }
 }
 
-//body.ie .popup-body {
-//  min-height: 100%;
-//}
-
 .corner-button {
   position: absolute;
   top: 0;
 .corner-button {
   position: absolute;
   top: 0;
@@ -82,7 +78,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   min-height: 70vh;
 }
 
   min-height: 70vh;
 }
 
-#image-manager .dropzone-container {
+.dropzone-container {
   position: relative;
   border: 3px dashed #DDD;
 }
   position: relative;
   border: 3px dashed #DDD;
 }
index a03a208b6539440cb2cdfa4b8adad76db3b63904..3a344b651827c737ab2fc483e8e7b743e5eb17ef 100644 (file)
@@ -4,6 +4,7 @@
     <div class="tabs primary-background-light">
         <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
         <span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
     <div class="tabs primary-background-light">
         <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
         <span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
+        <span tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span>
     </div>
 
     <div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
     </div>
 
     <div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
         </div>
     </div>
 
         </div>
     </div>
 
+    <div tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
+        <h4>Attached Files</h4>
+        <div class="padded files">
+            <p class="muted small">Upload some files to display on your page. This are visible in the page sidebar.</p>
+            <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
+
+            <table class="no-style" tag-autosuggestions style="width: 100%;">
+                <tbody ui-sortable="sortOptions" ng-model="files" >
+                <tr ng-repeat="file in files track by $index">
+                    <td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
+                    <td ng-bind="file.name"></td>
+                    <td width="10" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+    </div>
+
 </div>
\ No newline at end of file
 </div>
\ No newline at end of file
index 19541734ba8b3dd8fffed807b11f1c6a919972b4..7e05af48379ccb5bf1e06ba8398ecc8d8b0c5a70 100644 (file)
@@ -87,6 +87,11 @@ Route::group(['middleware' => 'auth'], function () {
         Route::delete('/{imageId}', 'ImageController@destroy');
     });
 
         Route::delete('/{imageId}', 'ImageController@destroy');
     });
 
+    // File routes
+    Route::post('/files/upload', 'FileController@upload');
+    Route::get('/files/get/page/{pageId}', 'FileController@getFilesForPage');
+    Route::put('/files/sort/page/{pageId}', 'FileController@sortFilesForPage');
+
     // AJAX routes
     Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');
     Route::get('/ajax/page/{id}', 'PageController@getPageAjax');
     // AJAX routes
     Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');
     Route::get('/ajax/page/{id}', 'PageController@getPageAjax');
diff --git a/storage/uploads/files/.gitignore b/storage/uploads/files/.gitignore
new file mode 100755 (executable)
index 0000000..d6b7ef3
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!.gitignore