From 4c574c22a80a6838f0cf0b37b9fab1242e4fdf8c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 7 Dec 2018 18:33:32 +0000 Subject: [PATCH] Implemented functionality to make books sort function Also changed public user settings to be stored in session rather than DB. Cleaned existing list view type logic. --- app/Entities/Entity.php | 5 ++ app/Entities/Repos/EntityRepo.php | 21 ++++- app/Http/Controllers/BookController.php | 18 ++++- app/Http/Controllers/Controller.php | 14 ++++ app/Http/Controllers/UserController.php | 80 ++++++++++++++----- app/Settings/SettingService.php | 6 ++ resources/assets/js/components/index.js | 4 +- .../assets/js/components/list-sort-control.js | 42 ++++++++++ resources/assets/sass/_lists.scss | 3 + resources/assets/sass/styles.scss | 3 + resources/lang/en/common.php | 7 ++ resources/views/books/index.blade.php | 4 +- resources/views/books/list.blade.php | 21 +---- resources/views/books/view-toggle.blade.php | 4 +- resources/views/partials/sort.blade.php | 32 ++++++++ routes/web.php | 1 + 16 files changed, 218 insertions(+), 47 deletions(-) create mode 100644 resources/assets/js/components/list-sort-control.js create mode 100644 resources/views/partials/sort.blade.php diff --git a/app/Entities/Entity.php b/app/Entities/Entity.php index 7917f83f8..d648f68e4 100644 --- a/app/Entities/Entity.php +++ b/app/Entities/Entity.php @@ -102,6 +102,11 @@ class Entity extends Ownable 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 diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index 44fb9ad12..13a9d9a9c 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -15,6 +15,7 @@ use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; use BookStack\Uploads\AttachmentService; use DOMDocument; +use Illuminate\Database\Eloquent\Builder; 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 + * @param string $sort + * @param string $order * @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; } /** diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 44368a9c4..b5e2a4a85 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -36,18 +36,30 @@ class BookController extends Controller */ 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); - $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, - 'booksViewType' => $booksViewType + 'view' => $view, + 'sort' => $sort, + 'order' => $order, + 'sortOptions' => $sortOptions, ]); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 80f567eaa..fc4f184fc 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -123,6 +123,20 @@ abstract class Controller extends BaseController 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 diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 5f5c8365e..92dc3cdaa 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -247,19 +247,7 @@ class UserController extends Controller */ public function switchBookView($id, Request $request) { - $this->checkPermissionOr('users-manage', function () use ($id) { - return $this->currentUser->id == $id; - }); - - $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); - - return redirect()->back(302, [], "/settings/users/$id"); + return $this->switchViewType($id, $request, 'books'); } /** @@ -270,18 +258,72 @@ class UserController extends Controller */ public function switchShelfView($id, Request $request) { - $this->checkPermissionOr('users-manage', function () use ($id) { - return $this->currentUser->id == $id; - }); + 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'; } - $user = $this->user->findOrFail($id); - setting()->putUser($user, 'bookshelves_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"); } + + /** + * Change the stored sort type for the books view. + * @param $id + * @param Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function changeBooksSort($id, Request $request) + { + // TODO - Test this endpoint + return $this->changeListSort($id, $request, 'books'); + } + + /** + * 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'; + } + + $order = $request->get('order'); + if (!in_array($order, ['asc', 'desc'])) { + $order = 'asc'; + } + + $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"); + } + } diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index c903bd60a..42a381060 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -60,6 +60,9 @@ class SettingService */ public function getUser($user, $key, $default = false) { + if ($user->isDefault()) { + return session()->get($key, $default); + } return $this->get($this->userKey($user->id, $key), $default); } @@ -179,6 +182,9 @@ class SettingService */ public function putUser($user, $key, $value) { + if ($user->isDefault()) { + return session()->put($key, $value); + } return $this->put($this->userKey($user->id, $key), $value); } diff --git a/resources/assets/js/components/index.js b/resources/assets/js/components/index.js index 7007b5878..dd1d95a31 100644 --- a/resources/assets/js/components/index.js +++ b/resources/assets/js/components/index.js @@ -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 listSortControl from "./list-sort-control"; const componentMapping = { @@ -42,7 +43,8 @@ const componentMapping = { 'page-display': pageDisplay, 'shelf-sort': shelfSort, 'homepage-control': homepageControl, - 'header-mobile-toggle': headerMobileToggle, + 'header-mobile-toggle': headerMobileToggle, + 'list-sort-control': listSortControl, }; 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 index 000000000..d463ed0b7 --- /dev/null +++ b/resources/assets/js/components/list-sort-control.js @@ -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 diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index 74d32e143..55104680a 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -369,6 +369,9 @@ ul.pagination { padding: $-xs $-m; line-height: 1.2; } + li.active a { + font-weight: 600; + } a, button { display: block; padding: $-xs $-m; diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index ff88cb1d4..7792aee83 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -258,6 +258,9 @@ $btt-size: 40px; .list-sort-container { display: inline-block; + form { + display: inline-block; + } .list-sort { display: inline-grid; margin-left: $-s; diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 8e86129e2..3e42c9feb 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -40,6 +40,13 @@ return [ 'remove' => 'Remove', 'add' => 'Add', + /** + * Sort Options + */ + 'sort_name' => 'Name', + 'sort_created_at' => 'Created Date', + 'sort_updated_at' => 'Updated Date', + /** * Misc */ diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index b1998da3b..3cf1a10bf 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -28,7 +28,7 @@ @stop @section('body') - @include('books.list', ['books' => $books, 'bookViewType' => $booksViewType]) + @include('books.list', ['books' => $books, 'view' => $view]) @stop @section('right') @@ -42,7 +42,7 @@ {{ trans('entities.books_create') }} @endif - @include('books.view-toggle', ['booksViewType' => $booksViewType]) + @include('books.view-toggle', ['view' => $view]) diff --git a/resources/views/books/list.blade.php b/resources/views/books/list.blade.php index 0e5f5c887..2155cd5c8 100644 --- a/resources/views/books/list.blade.php +++ b/resources/views/books/list.blade.php @@ -1,30 +1,15 @@ -
+

{{ trans('entities.books') }}

-
-
Sort
-
- -
- @icon('sort-up') -
-
-
+ @include('partials.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort])
@if(count($books) > 0) - @if($booksViewType === 'list') + @if($view === 'list')
@foreach($books as $book) diff --git a/resources/views/books/view-toggle.blade.php b/resources/views/books/view-toggle.blade.php index eb1464b02..c0f8b3f15 100644 --- a/resources/views/books/view-toggle.blade.php +++ b/resources/views/books/view-toggle.blade.php @@ -2,8 +2,8 @@
id}/switch-book-view") }}" method="POST" class="inline"> {!! csrf_field() !!} {!! method_field('PATCH') !!} - - @if ($booksViewType === 'list') + + @if ($view === 'list') @icon('grid') {{ trans('common.grid_view') }} diff --git a/resources/views/partials/sort.blade.php b/resources/views/partials/sort.blade.php new file mode 100644 index 000000000..03eab8487 --- /dev/null +++ b/resources/views/partials/sort.blade.php @@ -0,0 +1,32 @@ + +
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index d3c5f46d3..bfdd0e580 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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::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');