]> BookStack Code Mirror - bookstack/commitdiff
Implemented functionality to make books sort function
authorDan Brown <redacted>
Fri, 7 Dec 2018 18:33:32 +0000 (18:33 +0000)
committerDan Brown <redacted>
Fri, 7 Dec 2018 18:33:53 +0000 (18:33 +0000)
Also changed public user settings to be stored in session rather than DB.
Cleaned existing list view type logic.

16 files changed:
app/Entities/Entity.php
app/Entities/Repos/EntityRepo.php
app/Http/Controllers/BookController.php
app/Http/Controllers/Controller.php
app/Http/Controllers/UserController.php
app/Settings/SettingService.php
resources/assets/js/components/index.js
resources/assets/js/components/list-sort-control.js [new file with mode: 0644]
resources/assets/sass/_lists.scss
resources/assets/sass/styles.scss
resources/lang/en/common.php
resources/views/books/index.blade.php
resources/views/books/list.blade.php
resources/views/books/view-toggle.blade.php
resources/views/partials/sort.blade.php [new file with mode: 0644]
routes/web.php

index 7917f83f83648f95aa3a2daa0741d0a018e00647..d648f68e45869b1e8c514249a87c513ed88037e7 100644 (file)
@@ -102,6 +102,11 @@ class Entity extends Ownable
         return $this->morphMany(View::class, 'viewable');
     }
 
         return $this->morphMany(View::class, 'viewable');
     }
 
+    public function viewCountQuery()
+    {
+        return $this->views()->selectRaw('viewable_id, sum(views) as view_count')->groupBy('viewable_id');
+    }
+
     /**
      * Get the Tag models that have been user assigned to this entity.
      * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     /**
      * Get the Tag models that have been user assigned to this entity.
      * @return \Illuminate\Database\Eloquent\Relations\MorphMany
index 44fb9ad123dba1ee4de72e70a8bd2e2f90126844..13a9d9a9c33ec3097a71560efb1df70e8bd97d73 100644 (file)
@@ -15,6 +15,7 @@ use BookStack\Exceptions\NotFoundException;
 use BookStack\Exceptions\NotifyException;
 use BookStack\Uploads\AttachmentService;
 use DOMDocument;
 use BookStack\Exceptions\NotifyException;
 use BookStack\Uploads\AttachmentService;
 use DOMDocument;
+use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Http\Request;
 use Illuminate\Support\Collection;
 
 use Illuminate\Http\Request;
 use Illuminate\Support\Collection;
 
@@ -179,11 +180,27 @@ class EntityRepo
      * Get all entities in a paginated format
      * @param $type
      * @param int $count
      * Get all entities in a paginated format
      * @param $type
      * @param int $count
+     * @param string $sort
+     * @param string $order
      * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
      */
      * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
      */
-    public function getAllPaginated($type, $count = 10)
+    public function getAllPaginated($type, int $count = 10, string $sort = 'name', string $order = 'asc')
     {
     {
-        return $this->entityQuery($type)->orderBy('name', 'asc')->paginate($count);
+        $query = $this->entityQuery($type);
+        $query = $this->addSortToQuery($query, $sort, $order);
+        return $query->paginate($count);
+    }
+
+    protected function addSortToQuery(Builder $query, string $sort = 'name', string $order = 'asc')
+    {
+        $order = ($order === 'asc') ? 'asc' : 'desc';
+        $propertySorts = ['name', 'created_at', 'updated_at'];
+
+        if (in_array($sort, $propertySorts)) {
+            return $query->orderBy($sort, $order);
+        }
+
+        return $query;
     }
 
     /**
     }
 
     /**
index 44368a9c4fb66afb47dc2ed62b70f7a51719707e..b5e2a4a8553257d7228607b33074026d5c97b943 100644 (file)
@@ -36,18 +36,30 @@ class BookController extends Controller
      */
     public function index()
     {
      */
     public function index()
     {
-        $books = $this->entityRepo->getAllPaginated('book', 18);
+        $view = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books'));
+        $sort = setting()->getUser($this->currentUser, 'books_sort', 'name');
+        $order = setting()->getUser($this->currentUser, 'books_sort_order', 'asc');
+        $sortOptions = [
+            'name' => trans('common.sort_name'),
+            'created_at' => trans('common.sort_created_at'),
+            'updated_at' => trans('common.sort_updated_at'),
+        ];
+
+        $books = $this->entityRepo->getAllPaginated('book', 18, $sort, $order);
         $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
         $popular = $this->entityRepo->getPopular('book', 4, 0);
         $new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
         $recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('book', 4, 0) : false;
         $popular = $this->entityRepo->getPopular('book', 4, 0);
         $new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
-        $booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
+
         $this->setPageTitle(trans('entities.books'));
         return view('books/index', [
             'books' => $books,
             'recents' => $recents,
             'popular' => $popular,
             'new' => $new,
         $this->setPageTitle(trans('entities.books'));
         return view('books/index', [
             'books' => $books,
             'recents' => $recents,
             'popular' => $popular,
             'new' => $new,
-            'booksViewType' => $booksViewType
+            'view' => $view,
+            'sort' => $sort,
+            'order' => $order,
+            'sortOptions' => $sortOptions,
         ]);
     }
 
         ]);
     }
 
index 80f567eaa80279e8f49599f9703f5ab29e6276c3..fc4f184fcabdff0bf292b6f6c257912ce6d98a2d 100644 (file)
@@ -123,6 +123,20 @@ abstract class Controller extends BaseController
         return true;
     }
 
         return true;
     }
 
+    /**
+     * Check if the current user has a permission or bypass if the provided user
+     * id matches the current user.
+     * @param string $permissionName
+     * @param int $userId
+     * @return bool
+     */
+    protected function checkPermissionOrCurrentUser(string $permissionName, int $userId)
+    {
+        return $this->checkPermissionOr($permissionName, function() use ($userId) {
+            return $userId === $this->currentUser->id;
+        });
+    }
+
     /**
      * Send back a json error message.
      * @param string $messageText
     /**
      * Send back a json error message.
      * @param string $messageText
index 5f5c8365eecc34de70ef0dae569f2c20406a79e2..92dc3cdaaa9d00f478c46e72a06b70c9f03d3579 100644 (file)
@@ -247,41 +247,83 @@ class UserController extends Controller
      */
     public function switchBookView($id, Request $request)
     {
      */
     public function switchBookView($id, Request $request)
     {
-        $this->checkPermissionOr('users-manage', function () use ($id) {
-            return $this->currentUser->id == $id;
-        });
+        return $this->switchViewType($id, $request, 'books');
+    }
+
+    /**
+     * Update the user's preferred shelf-list display setting.
+     * @param $id
+     * @param Request $request
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function switchShelfView($id, Request $request)
+    {
+        return $this->switchViewType($id, $request, 'bookshelves');
+    }
+
+    /**
+     * For a type of list, switch with stored view type for a user.
+     * @param integer $userId
+     * @param Request $request
+     * @param string $listName
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    protected function switchViewType($userId, Request $request, string $listName)
+    {
+        $this->checkPermissionOrCurrentUser('users-manage', $userId);
 
         $viewType = $request->get('view_type');
         if (!in_array($viewType, ['grid', 'list'])) {
             $viewType = 'list';
         }
 
 
         $viewType = $request->get('view_type');
         if (!in_array($viewType, ['grid', 'list'])) {
             $viewType = 'list';
         }
 
-        $user = $this->user->findOrFail($id);
-        setting()->putUser($user, 'books_view_type', $viewType);
+        $user = $this->user->findOrFail($userId);
+        $key = $listName . '_view_type';
+        setting()->putUser($user, $key, $viewType);
 
 
-        return redirect()->back(302, [], "/settings/users/$id");
+        return redirect()->back(302, [], "/settings/users/$userId");
     }
 
     /**
     }
 
     /**
-     * Update the user's preferred shelf-list display setting.
+     * Change the stored sort type for the books view.
      * @param $id
      * @param Request $request
      * @return \Illuminate\Http\RedirectResponse
      */
      * @param $id
      * @param Request $request
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function switchShelfView($id, Request $request)
+    public function changeBooksSort($id, Request $request)
     {
     {
-        $this->checkPermissionOr('users-manage', function () use ($id) {
-            return $this->currentUser->id == $id;
-        });
+        // TODO - Test this endpoint
+        return $this->changeListSort($id, $request, 'books');
+    }
 
 
-        $viewType = $request->get('view_type');
-        if (!in_array($viewType, ['grid', 'list'])) {
-            $viewType = 'list';
+    /**
+     * Changed the stored preference for a list sort order.
+     * @param int $userId
+     * @param Request $request
+     * @param string $listName
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    protected function changeListSort(int $userId, Request $request, string $listName)
+    {
+        $this->checkPermissionOrCurrentUser('users-manage', $userId);
+
+        $sort = $request->get('sort');
+        if (!in_array($sort, ['name', 'created_at', 'updated_at'])) {
+            $sort = 'name';
         }
 
         }
 
-        $user = $this->user->findOrFail($id);
-        setting()->putUser($user, 'bookshelves_view_type', $viewType);
+        $order = $request->get('order');
+        if (!in_array($order, ['asc', 'desc'])) {
+            $order = 'asc';
+        }
 
 
-        return redirect()->back(302, [], "/settings/users/$id");
+        $user = $this->user->findOrFail($userId);
+        $sortKey = $listName . '_sort';
+        $orderKey = $listName . '_sort_order';
+        setting()->putUser($user, $sortKey, $sort);
+        setting()->putUser($user, $orderKey, $order);
+
+        return redirect()->back(302, [], "/settings/users/$userId");
     }
     }
+
 }
 }
index c903bd60a5dc4b811bcf2604965aa3692d020382..42a3810608750c9afbdf4e3352c29350d9511d4d 100644 (file)
@@ -60,6 +60,9 @@ class SettingService
      */
     public function getUser($user, $key, $default = false)
     {
      */
     public function getUser($user, $key, $default = false)
     {
+        if ($user->isDefault()) {
+            return session()->get($key, $default);
+        }
         return $this->get($this->userKey($user->id, $key), $default);
     }
 
         return $this->get($this->userKey($user->id, $key), $default);
     }
 
@@ -179,6 +182,9 @@ class SettingService
      */
     public function putUser($user, $key, $value)
     {
      */
     public function putUser($user, $key, $value)
     {
+        if ($user->isDefault()) {
+            return session()->put($key, $value);
+        }
         return $this->put($this->userKey($user->id, $key), $value);
     }
 
         return $this->put($this->userKey($user->id, $key), $value);
     }
 
index 7007b587827d16746ec588d468768648ca922693..dd1d95a31613b9136e75f7b1008c6f5a29977a27 100644 (file)
@@ -19,6 +19,7 @@ import pageDisplay from "./page-display";
 import shelfSort from "./shelf-sort";
 import homepageControl from "./homepage-control";
 import headerMobileToggle from "./header-mobile-toggle";
 import shelfSort from "./shelf-sort";
 import homepageControl from "./homepage-control";
 import headerMobileToggle from "./header-mobile-toggle";
+import listSortControl from "./list-sort-control";
 
 
 const componentMapping = {
 
 
 const componentMapping = {
@@ -42,7 +43,8 @@ const componentMapping = {
     'page-display': pageDisplay,
     'shelf-sort': shelfSort,
     'homepage-control': homepageControl,
     'page-display': pageDisplay,
     'shelf-sort': shelfSort,
     'homepage-control': homepageControl,
-     'header-mobile-toggle': headerMobileToggle,
+    'header-mobile-toggle': headerMobileToggle,
+    'list-sort-control': listSortControl,
 };
 
 window.components = {};
 };
 
 window.components = {};
diff --git a/resources/assets/js/components/list-sort-control.js b/resources/assets/js/components/list-sort-control.js
new file mode 100644 (file)
index 0000000..d463ed0
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * ListSortControl
+ * Manages the logic for the control which provides list sorting options.
+ */
+class ListSortControl {
+
+    constructor(elem) {
+        this.elem = elem;
+
+        this.sortInput = elem.querySelector('[name="sort"]');
+        this.orderInput = elem.querySelector('[name="order"]');
+        this.form = elem.querySelector('form');
+
+        this.elem.addEventListener('click', event => {
+            if (event.target.closest('[data-sort-value]') !== null) {
+                this.sortOptionClick(event);
+            }
+            if (event.target.closest('[data-sort-dir]') !== null) {
+                this.sortDirectionClick(event);
+            }
+        })
+
+    }
+
+    sortOptionClick(event) {
+        const sortOption = event.target.closest('[data-sort-value]');
+        this.sortInput.value = sortOption.getAttribute('data-sort-value');
+        event.preventDefault();
+        this.form.submit();
+    }
+
+    sortDirectionClick(event) {
+        const currentDir = this.orderInput.value;
+        const newDir = (currentDir === 'asc') ? 'desc' : 'asc';
+        this.orderInput.value = newDir;
+        event.preventDefault();
+        this.form.submit();
+    }
+
+}
+
+export default ListSortControl;
\ No newline at end of file
index 74d32e1438f833c02e9bd2eb1131f96ec7012a38..55104680a2488df1a59a0ed91e0055aecad2d1ef 100644 (file)
@@ -369,6 +369,9 @@ ul.pagination {
     padding: $-xs $-m;
     line-height: 1.2;
   }
     padding: $-xs $-m;
     line-height: 1.2;
   }
+  li.active a {
+    font-weight: 600;
+  }
   a, button {
     display: block;
     padding: $-xs $-m;
   a, button {
     display: block;
     padding: $-xs $-m;
index ff88cb1d4aa2db0f2b96b7391a5e91b537ab4971..7792aee83056ee6b5779786dc845037efddeb7f6 100644 (file)
@@ -258,6 +258,9 @@ $btt-size: 40px;
 
 .list-sort-container {
   display: inline-block;
 
 .list-sort-container {
   display: inline-block;
+  form {
+    display: inline-block;
+  }
   .list-sort {
     display: inline-grid;
     margin-left: $-s;
   .list-sort {
     display: inline-grid;
     margin-left: $-s;
index 8e86129e2d49581fb8e025e8d41b8fb41e72a9d0..3e42c9feb4abdc1ec445813182dd4f0475e44769 100644 (file)
@@ -40,6 +40,13 @@ return [
     'remove' => 'Remove',
     'add' => 'Add',
 
     'remove' => 'Remove',
     'add' => 'Add',
 
+    /**
+     * Sort Options
+     */
+    'sort_name' => 'Name',
+    'sort_created_at' => 'Created Date',
+    'sort_updated_at' => 'Updated Date',
+
     /**
      * Misc
      */
     /**
      * Misc
      */
index b1998da3baff9afb81ed6e7dda5559b63d547be5..3cf1a10bf201991821e29195b4d238718f1e5b33 100644 (file)
@@ -28,7 +28,7 @@
 @stop
 
 @section('body')
 @stop
 
 @section('body')
-    @include('books.list', ['books' => $books, 'bookViewType' => $booksViewType])
+    @include('books.list', ['books' => $books, 'view' => $view])
 @stop
 
 @section('right')
 @stop
 
 @section('right')
@@ -42,7 +42,7 @@
                     <span>{{ trans('entities.books_create') }}</span>
                 </a>
             @endif
                     <span>{{ trans('entities.books_create') }}</span>
                 </a>
             @endif
-            @include('books.view-toggle', ['booksViewType' => $booksViewType])
+            @include('books.view-toggle', ['view' => $view])
         </div>
     </div>
 
         </div>
     </div>
 
index 0e5f5c88718822fb870e065ae95d70ec294c9e6e..2155cd5c833b7b8fddc1adddf9b44fcf3db47dae 100644 (file)
@@ -1,30 +1,15 @@
 
 
-<div class="content-wrap card {{ $booksViewType === 'list' ? 'thin' : '' }}">
+<div class="content-wrap card {{ $view === 'list' ? 'thin' : '' }}">
     <div class="grid halves v-center">
         <h1 class="list-heading">{{ trans('entities.books') }}</h1>
         <div class="text-right">
 
     <div class="grid halves v-center">
         <h1 class="list-heading">{{ trans('entities.books') }}</h1>
         <div class="text-right">
 
-            <div class="list-sort-container">
-                <div class="list-sort-label">Sort</div>
-                <div class="list-sort">
-                    <div class="list-sort-type dropdown-container" dropdown>
-                        <div dropdown-toggle>Name</div>
-                        <ul>
-                            <li><a href="#">Name</a></li>
-                            <li><a href="#">Created Date</a></li>
-                            <li><a href="#">Popularity</a></li>
-                        </ul>
-                    </div>
-                    <div class="list-sort-dir">
-                        @icon('sort-up')
-                    </div>
-                </div>
-            </div>
+            @include('partials.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort])
 
         </div>
     </div>
     @if(count($books) > 0)
 
         </div>
     </div>
     @if(count($books) > 0)
-        @if($booksViewType === 'list')
+        @if($view === 'list')
             <div class="entity-list">
                 @foreach($books as $book)
                     <a href="{{ $book->getUrl() }}" class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
             <div class="entity-list">
                 @foreach($books as $book)
                     <a href="{{ $book->getUrl() }}" class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
index eb1464b021c29ba7d3b6d712d95757b223badb29..c0f8b3f15c207d1451c215b62f394dadfcbb49f1 100644 (file)
@@ -2,8 +2,8 @@
     <form action="{{ baseUrl("/settings/users/{$currentUser->id}/switch-book-view") }}" method="POST" class="inline">
         {!! csrf_field() !!}
         {!! method_field('PATCH') !!}
     <form action="{{ baseUrl("/settings/users/{$currentUser->id}/switch-book-view") }}" method="POST" class="inline">
         {!! csrf_field() !!}
         {!! method_field('PATCH') !!}
-        <input type="hidden" value="{{ $booksViewType === 'list'? 'grid' : 'list' }}" name="view_type">
-        @if ($booksViewType === 'list')
+        <input type="hidden" value="{{ $view === 'list'? 'grid' : 'list' }}" name="view_type">
+        @if ($view === 'list')
             <a onclick="this.closest('form').submit()" type="submit" class="icon-list-item">
                 <span class="icon">@icon('grid')</span>
                 <span>{{ trans('common.grid_view') }}</span>
             <a onclick="this.closest('form').submit()" type="submit" class="icon-list-item">
                 <span class="icon">@icon('grid')</span>
                 <span>{{ trans('common.grid_view') }}</span>
diff --git a/resources/views/partials/sort.blade.php b/resources/views/partials/sort.blade.php
new file mode 100644 (file)
index 0000000..03eab84
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+    $selectedSort = (isset($sort) && array_key_exists($sort, $options)) ? $sort : array_keys($options)[0];
+    $order = (isset($order) && in_array($order, ['asc', 'desc'])) ? $order : 'asc';
+?>
+<div class="list-sort-container" list-sort-control>
+    <div class="list-sort-label">{{ trans('common.sort') }}</div>
+    <form action="{{ baseUrl("/settings/users/{$currentUser->id}/change-books-sort") }}" method="post">
+
+        {!! csrf_field() !!}
+        {!! method_field('PATCH') !!}
+        <input type="hidden" value="{{ $selectedSort }}" name="sort">
+        <input type="hidden" value="{{ $order }}" name="order">
+
+        <div class="list-sort">
+            <div class="list-sort-type dropdown-container" dropdown>
+                <div dropdown-toggle>{{ $options[$selectedSort] }}</div>
+                <ul>
+                    @foreach($options as $key => $label)
+                        <li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}">{{ $label }}</a></li>
+                    @endforeach
+                </ul>
+            </div>
+            <div class="list-sort-dir" data-sort-dir>
+                @if($order === 'desc')
+                    @icon('sort-up')
+                @else
+                    @icon('sort-down')
+                @endif
+            </div>
+        </div>
+    </form>
+</div>
\ No newline at end of file
index d3c5f46d3ccdce717e167d4e545b4f19639feb68..bfdd0e580d43c5e613988e5d175c76639b63513b 100644 (file)
@@ -176,6 +176,7 @@ Route::group(['middleware' => 'auth'], function () {
         Route::get('/users/{id}/delete', 'UserController@delete');
         Route::patch('/users/{id}/switch-book-view', 'UserController@switchBookView');
         Route::patch('/users/{id}/switch-shelf-view', 'UserController@switchShelfView');
         Route::get('/users/{id}/delete', 'UserController@delete');
         Route::patch('/users/{id}/switch-book-view', 'UserController@switchBookView');
         Route::patch('/users/{id}/switch-shelf-view', 'UserController@switchShelfView');
+        Route::patch('/users/{id}/change-books-sort', 'UserController@changeBooksSort');
         Route::post('/users/create', 'UserController@store');
         Route::get('/users/{id}', 'UserController@edit');
         Route::put('/users/{id}', 'UserController@update');
         Route::post('/users/create', 'UserController@store');
         Route::get('/users/{id}', 'UserController@edit');
         Route::put('/users/{id}', 'UserController@update');