- 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.
{
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;
+ }
}
--- /dev/null
+<?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);
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?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);
+ }
+
+}
\ No newline at end of file
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;
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();
}
$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,
/**
* 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,
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;
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();
}
*/
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 = [
'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,
*/
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', [
<?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)
* 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)
* 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)
/**
* 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)
{
$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']);
}
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;
return substr_count($uploadName, '.') < 2;
});
-
// Custom blade view directives
Blade::directive('icon', function ($expression) {
return "<?php echo icon($expression); ?>";
'BookStack\\Chapter' => Chapter::class,
'BookStack\\Page' => Page::class,
]);
+
+ // View Composers
+ View::composer('partials.breadcrumbs', BreadcrumbsViewComposer::class);
}
/**
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';
}
}
<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>
$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());
+ }
+
}