mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-01-19 05:52:48 +08:00
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:
parent
0b976d9f91
commit
4c574c22a8
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
42
resources/assets/js/components/list-sort-control.js
Normal file
42
resources/assets/js/components/list-sort-control.js
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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}}">
|
||||
|
|
|
@ -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>
|
||||
|
|
32
resources/views/partials/sort.blade.php
Normal file
32
resources/views/partials/sort.blade.php
Normal 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>
|
|
@ -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');
|
||||
|
|
Loading…
Reference in New Issue
Block a user