Also changed public user settings to be stored in session rather than DB.
Cleaned existing list view type logic.
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
use BookStack\Exceptions\NotifyException;
use BookStack\Uploads\AttachmentService;
use DOMDocument;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
* 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;
}
/**
*/
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,
]);
}
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
*/
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';
}
- $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
*/
- 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");
}
+
}
*/
public function getUser($user, $key, $default = false)
{
+ if ($user->isDefault()) {
+ return session()->get($key, $default);
+ }
return $this->get($this->userKey($user->id, $key), $default);
}
*/
public function putUser($user, $key, $value)
{
+ if ($user->isDefault()) {
+ return session()->put($key, $value);
+ }
return $this->put($this->userKey($user->id, $key), $value);
}
import shelfSort from "./shelf-sort";
import homepageControl from "./homepage-control";
import headerMobileToggle from "./header-mobile-toggle";
+import listSortControl from "./list-sort-control";
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 = {};
--- /dev/null
+/**
+ * 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
padding: $-xs $-m;
line-height: 1.2;
}
+ li.active a {
+ font-weight: 600;
+ }
a, button {
display: block;
padding: $-xs $-m;
.list-sort-container {
display: inline-block;
+ form {
+ display: inline-block;
+ }
.list-sort {
display: inline-grid;
margin-left: $-s;
'remove' => 'Remove',
'add' => 'Add',
+ /**
+ * Sort Options
+ */
+ 'sort_name' => 'Name',
+ 'sort_created_at' => 'Created Date',
+ 'sort_updated_at' => 'Updated Date',
+
/**
* Misc
*/
@stop
@section('body')
- @include('books.list', ['books' => $books, 'bookViewType' => $booksViewType])
+ @include('books.list', ['books' => $books, 'view' => $view])
@stop
@section('right')
<span>{{ trans('entities.books_create') }}</span>
</a>
@endif
- @include('books.view-toggle', ['booksViewType' => $booksViewType])
+ @include('books.view-toggle', ['view' => $view])
</div>
</div>
-<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}}">
<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>
--- /dev/null
+<?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
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');