mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-23 11:22:33 +01:00
Added bookshelves to breadcrumbs
- Updated breadcrumb dropdown switchers and back-end sibling code to handle new breadcrumbs. - Added breadcrumb view composer and EntityContext system to mangage tracking if in the context of a bookshelf.
This commit is contained in:
parent
221a483b40
commit
b12ae6d11b
@ -92,4 +92,14 @@ class Bookshelf extends Entity
|
||||
{
|
||||
return "'BookStack\\\\BookShelf' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this shelf contains the given book.
|
||||
* @param Book $book
|
||||
* @return bool
|
||||
*/
|
||||
public function contains(Book $book)
|
||||
{
|
||||
return $this->books()->where('id', '=', $book->id)->count() > 0;
|
||||
}
|
||||
}
|
||||
|
34
app/Entities/BreadcrumbsViewComposer.php
Normal file
34
app/Entities/BreadcrumbsViewComposer.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
||||
class BreadcrumbsViewComposer
|
||||
{
|
||||
|
||||
protected $entityContextManager;
|
||||
|
||||
/**
|
||||
* BreadcrumbsViewComposer constructor.
|
||||
* @param EntityContextManager $entityContextManager
|
||||
*/
|
||||
public function __construct(EntityContextManager $entityContextManager)
|
||||
{
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify data when the view is composed.
|
||||
* @param View $view
|
||||
*/
|
||||
public function compose(View $view)
|
||||
{
|
||||
$crumbs = $view->getData()['crumbs'];
|
||||
if (array_first($crumbs) instanceof Book) {
|
||||
$shelf = $this->entityContextManager->getContextualShelfForBook(array_first($crumbs));
|
||||
if ($shelf) {
|
||||
array_unshift($crumbs, $shelf);
|
||||
$view->with('crumbs', $crumbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
app/Entities/EntityContextManager.php
Normal file
62
app/Entities/EntityContextManager.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php namespace BookStack\Entities;
|
||||
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use Illuminate\Session\Store;
|
||||
|
||||
class EntityContextManager
|
||||
{
|
||||
protected $session;
|
||||
protected $entityRepo;
|
||||
|
||||
protected $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id';
|
||||
|
||||
/**
|
||||
* EntityContextManager constructor.
|
||||
* @param Store $session
|
||||
* @param EntityRepo $entityRepo
|
||||
*/
|
||||
public function __construct(Store $session, EntityRepo $entityRepo)
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->entityRepo = $entityRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current bookshelf context for the given book.
|
||||
* @param Book $book
|
||||
* @return Bookshelf|null
|
||||
*/
|
||||
public function getContextualShelfForBook(Book $book)
|
||||
{
|
||||
$contextBookshelfId = $this->session->get($this->KEY_SHELF_CONTEXT_ID, null);
|
||||
if (is_int($contextBookshelfId)) {
|
||||
|
||||
/** @var Bookshelf $shelf */
|
||||
$shelf = $this->entityRepo->getById('bookshelf', $contextBookshelfId);
|
||||
|
||||
if ($shelf && $shelf->contains($book)) {
|
||||
return $shelf;
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current contextual shelf ID.
|
||||
* @param int $shelfId
|
||||
*/
|
||||
public function setShelfContext(int $shelfId)
|
||||
{
|
||||
$this->session->put($this->KEY_SHELF_CONTEXT_ID, $shelfId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the session stored shelf context id.
|
||||
*/
|
||||
public function clearShelfContext()
|
||||
{
|
||||
$this->session->forget($this->KEY_SHELF_CONTEXT_ID);
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
use Activity;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\EntityContextManager;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Entities\ExportService;
|
||||
use Illuminate\Http\Request;
|
||||
@ -15,18 +16,25 @@ class BookController extends Controller
|
||||
protected $entityRepo;
|
||||
protected $userRepo;
|
||||
protected $exportService;
|
||||
protected $entityContextManager;
|
||||
|
||||
/**
|
||||
* BookController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param \BookStack\Auth\UserRepo $userRepo
|
||||
* @param \BookStack\Entities\ExportService $exportService
|
||||
* @param UserRepo $userRepo
|
||||
* @param ExportService $exportService
|
||||
* @param EntityContextManager $entityContextManager
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
|
||||
{
|
||||
public function __construct(
|
||||
EntityRepo $entityRepo,
|
||||
UserRepo $userRepo,
|
||||
ExportService $exportService,
|
||||
EntityContextManager $entityContextManager
|
||||
) {
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->exportService = $exportService;
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -50,6 +58,8 @@ class BookController extends Controller
|
||||
$popular = $this->entityRepo->getPopular('book', 4, 0);
|
||||
$new = $this->entityRepo->getRecentlyCreated('book', 4, 0);
|
||||
|
||||
$this->entityContextManager->clearShelfContext();
|
||||
|
||||
$this->setPageTitle(trans('entities.books'));
|
||||
return view('books.index', [
|
||||
'books' => $books,
|
||||
@ -95,14 +105,22 @@ class BookController extends Controller
|
||||
/**
|
||||
* Display the specified book.
|
||||
* @param $slug
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
*/
|
||||
public function show($slug)
|
||||
public function show($slug, Request $request)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $slug);
|
||||
$this->checkOwnablePermission('book-view', $book);
|
||||
|
||||
$bookChildren = $this->entityRepo->getBookChildren($book);
|
||||
|
||||
Views::add($book);
|
||||
if ($request->has('shelf')) {
|
||||
$this->entityContextManager->setShelfContext(intval($request->get('shelf')));
|
||||
}
|
||||
|
||||
$this->setPageTitle($book->getShortName());
|
||||
return view('books.show', [
|
||||
'book' => $book,
|
||||
|
@ -3,6 +3,7 @@
|
||||
use Activity;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Entities\Bookshelf;
|
||||
use BookStack\Entities\EntityContextManager;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
@ -13,16 +14,19 @@ class BookshelfController extends Controller
|
||||
|
||||
protected $entityRepo;
|
||||
protected $userRepo;
|
||||
protected $entityContextManager;
|
||||
|
||||
/**
|
||||
* BookController constructor.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param UserRepo $userRepo
|
||||
* @param EntityContextManager $entityContextManager
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo)
|
||||
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, EntityContextManager $entityContextManager)
|
||||
{
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@ -32,9 +36,7 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
$view = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
|
||||
|
||||
$sort = setting()->getUser($this->currentUser, 'bookshelves_sort', 'name');
|
||||
$order = setting()->getUser($this->currentUser, 'bookshelves_sort_order', 'asc');
|
||||
$sortOptions = [
|
||||
@ -43,14 +45,16 @@ class BookshelfController extends Controller
|
||||
'updated_at' => trans('common.sort_updated_at'),
|
||||
];
|
||||
|
||||
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $sort, $order, function($query) {
|
||||
$query->with(['books']);
|
||||
});
|
||||
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18, $sort, $order);
|
||||
foreach ($shelves as $shelf) {
|
||||
$shelf->books = $this->entityRepo->getBookshelfChildren($shelf);
|
||||
}
|
||||
|
||||
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false;
|
||||
$popular = $this->entityRepo->getPopular('bookshelf', 4, 0);
|
||||
$new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0);
|
||||
|
||||
|
||||
$this->entityContextManager->clearShelfContext();
|
||||
$this->setPageTitle(trans('entities.shelves'));
|
||||
return view('shelves.index', [
|
||||
'shelves' => $shelves,
|
||||
@ -105,11 +109,13 @@ class BookshelfController extends Controller
|
||||
*/
|
||||
public function show(string $slug)
|
||||
{
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
|
||||
/** @var Bookshelf $bookshelf */
|
||||
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
|
||||
$this->checkOwnablePermission('book-view', $bookshelf);
|
||||
|
||||
$books = $this->entityRepo->getBookshelfChildren($bookshelf);
|
||||
Views::add($bookshelf);
|
||||
$this->entityContextManager->setShelfContext($bookshelf->id);
|
||||
|
||||
$this->setPageTitle($bookshelf->getShortName());
|
||||
return view('shelves.show', [
|
||||
|
@ -1,35 +1,45 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Actions\ViewService;
|
||||
use BookStack\Entities\EntityContextManager;
|
||||
use BookStack\Entities\Repos\EntityRepo;
|
||||
use BookStack\Entities\SearchService;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
protected $entityRepo;
|
||||
protected $viewService;
|
||||
protected $searchService;
|
||||
protected $entityContextManager;
|
||||
|
||||
/**
|
||||
* SearchController constructor.
|
||||
* @param \BookStack\Entities\Repos\EntityRepo $entityRepo
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param ViewService $viewService
|
||||
* @param SearchService $searchService
|
||||
* @param EntityContextManager $entityContextManager
|
||||
*/
|
||||
public function __construct(EntityRepo $entityRepo, ViewService $viewService, SearchService $searchService)
|
||||
{
|
||||
public function __construct(
|
||||
EntityRepo $entityRepo,
|
||||
ViewService $viewService,
|
||||
SearchService $searchService,
|
||||
EntityContextManager $entityContextManager
|
||||
) {
|
||||
$this->entityRepo = $entityRepo;
|
||||
$this->viewService = $viewService;
|
||||
$this->searchService = $searchService;
|
||||
$this->entityContextManager = $entityContextManager;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches all entities.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\View\View
|
||||
* @return View
|
||||
* @internal param string $searchTerm
|
||||
*/
|
||||
public function search(Request $request)
|
||||
@ -56,7 +66,7 @@ class SearchController extends Controller
|
||||
* Searches all entities within a book.
|
||||
* @param Request $request
|
||||
* @param integer $bookId
|
||||
* @return \Illuminate\View\View
|
||||
* @return View
|
||||
* @internal param string $searchTerm
|
||||
*/
|
||||
public function searchBook(Request $request, $bookId)
|
||||
@ -70,7 +80,7 @@ class SearchController extends Controller
|
||||
* Searches all entities within a chapter.
|
||||
* @param Request $request
|
||||
* @param integer $chapterId
|
||||
* @return \Illuminate\View\View
|
||||
* @return View
|
||||
* @internal param string $searchTerm
|
||||
*/
|
||||
public function searchChapter(Request $request, $chapterId)
|
||||
@ -106,7 +116,7 @@ class SearchController extends Controller
|
||||
/**
|
||||
* Search siblings items in the system.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|mixed
|
||||
* @return Factory|View|mixed
|
||||
*/
|
||||
public function searchSiblings(Request $request)
|
||||
{
|
||||
@ -130,16 +140,21 @@ class SearchController extends Controller
|
||||
$entities = $this->entityRepo->getBookDirectChildren($entity->book);
|
||||
}
|
||||
|
||||
// Book in shelf
|
||||
// TODO - When shelve tracking added, Update below if criteria
|
||||
|
||||
// Book
|
||||
// Gets just the books in a shelf if shelf is in context
|
||||
if ($entity->isA('book')) {
|
||||
$entities = $this->entityRepo->getAll('book');
|
||||
$contextShelf = $this->entityContextManager->getContextualShelfForBook($entity);
|
||||
if ($contextShelf) {
|
||||
$entities = $this->entityRepo->getBookshelfChildren($contextShelf);
|
||||
} else {
|
||||
$entities = $this->entityRepo->getAll('book');
|
||||
}
|
||||
}
|
||||
|
||||
// Shelve
|
||||
// TODO - When shelve tracking added
|
||||
if ($entity->isA('bookshelf')) {
|
||||
$entities = $this->entityRepo->getAll('bookshelf');
|
||||
}
|
||||
|
||||
return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']);
|
||||
}
|
||||
|
@ -3,12 +3,14 @@
|
||||
use Blade;
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Bookshelf;
|
||||
use BookStack\Entities\BreadcrumbsViewComposer;
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\Page;
|
||||
use BookStack\Settings\Setting;
|
||||
use BookStack\Settings\SettingService;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Schema;
|
||||
use Validator;
|
||||
@ -33,7 +35,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
return substr_count($uploadName, '.') < 2;
|
||||
});
|
||||
|
||||
|
||||
// Custom blade view directives
|
||||
Blade::directive('icon', function ($expression) {
|
||||
return "<?php echo icon($expression); ?>";
|
||||
@ -49,6 +50,9 @@ class AppServiceProvider extends ServiceProvider
|
||||
'BookStack\\Chapter' => Chapter::class,
|
||||
'BookStack\\Page' => Page::class,
|
||||
]);
|
||||
|
||||
// View Composers
|
||||
View::composer('partials.breadcrumbs', BreadcrumbsViewComposer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,10 +25,8 @@ class BreadcrumbListing {
|
||||
onSearch() {
|
||||
const input = this.searchInput.value.toLowerCase().trim();
|
||||
const listItems = this.entityListElem.querySelectorAll('.entity-list-item');
|
||||
console.log(listItems);
|
||||
for (let listItem of listItems) {
|
||||
const match = !input || listItem.textContent.toLowerCase().includes(input);
|
||||
console.log(match);
|
||||
listItem.style.display = match ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div class="entity-shelf-books grid third gap-y-xs entity-list-item-children">
|
||||
@foreach($shelf->books as $book)
|
||||
<div>
|
||||
<a href="{{ $book->getUrl() }}" class="entity-chip text-book">
|
||||
<a href="{{ $book->getUrl('?shelf=' . $shelf->id) }}" class="entity-chip text-book">
|
||||
@icon('book')
|
||||
{{ $book->name }}
|
||||
</a>
|
||||
|
@ -185,4 +185,30 @@ class BookShelfTest extends TestCase
|
||||
$this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
|
||||
}
|
||||
|
||||
public function test_bookshelves_show_in_breadcrumbs_if_in_context()
|
||||
{
|
||||
$shelf = Bookshelf::first();
|
||||
$shelfBook = $shelf->books()->first();
|
||||
$shelfPage = $shelfBook->pages()->first();
|
||||
$this->asAdmin();
|
||||
|
||||
$bookVisit = $this->get($shelfBook->getUrl());
|
||||
$bookVisit->assertElementNotContains('.breadcrumbs', 'Shelves');
|
||||
$bookVisit->assertElementNotContains('.breadcrumbs', $shelf->getShortName());
|
||||
|
||||
$this->get($shelf->getUrl());
|
||||
$bookVisit = $this->get($shelfBook->getUrl());
|
||||
$bookVisit->assertElementContains('.breadcrumbs', 'Shelves');
|
||||
$bookVisit->assertElementContains('.breadcrumbs', $shelf->getShortName());
|
||||
|
||||
$pageVisit = $this->get($shelfPage->getUrl());
|
||||
$pageVisit->assertElementContains('.breadcrumbs', 'Shelves');
|
||||
$pageVisit->assertElementContains('.breadcrumbs', $shelf->getShortName());
|
||||
|
||||
$this->get('/books');
|
||||
$pageVisit = $this->get($shelfPage->getUrl());
|
||||
$pageVisit->assertElementNotContains('.breadcrumbs', 'Shelves');
|
||||
$pageVisit->assertElementNotContains('.breadcrumbs', $shelf->getShortName());
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user