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.
This commit is contained in:
Dan Brown 2018-12-07 18:33:32 +00:00
parent 0b976d9f91
commit 4c574c22a8
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
16 changed files with 218 additions and 47 deletions

View File

@ -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

View File

@ -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;
}
/**

View File

@ -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,
]);
}

View File

@ -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

View File

@ -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");
}
}

View File

@ -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);
}

View 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 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 = {};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
*/

View File

@ -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 @@
<span>{{ trans('entities.books_create') }}</span>
</a>
@endif
@include('books.view-toggle', ['booksViewType' => $booksViewType])
@include('books.view-toggle', ['view' => $view])
</div>
</div>

View 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="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)
@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}}">

View File

@ -2,8 +2,8 @@
<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>

View File

@ -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>

View 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::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');