From 035a0d8efb1769d2816c1ebedb3f7cf12db1637e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 24 Feb 2019 15:57:35 +0000 Subject: [PATCH] Added experimental breadcrumb traversal --- app/Entities/Book.php | 9 +++ app/Entities/Repos/EntityRepo.php | 12 ++++ app/Http/Controllers/SearchController.php | 42 +++++++++++ resources/assets/icons/books.svg | 71 +++++++++++++++++++ .../js/components/breadcrumb-listing.js | 60 ++++++++++++++++ resources/assets/js/components/dropdown.js | 2 +- resources/assets/js/components/index.js | 3 +- resources/assets/sass/_header.scss | 44 ++++++++++++ resources/assets/sass/_text.scss | 5 +- resources/views/books/index.blade.php | 2 +- resources/views/books/show.blade.php | 6 ++ resources/views/common/header.blade.php | 2 +- .../partials/breadcrumb-listing.blade.php | 13 ++++ .../views/partials/breadcrumbs.blade.php | 23 +++++- .../partials/entity-list-basic.blade.php | 11 +++ resources/views/tri-layout.blade.php | 1 + routes/web.php | 1 + 17 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 resources/assets/icons/books.svg create mode 100644 resources/assets/js/components/breadcrumb-listing.js create mode 100644 resources/views/partials/breadcrumb-listing.blade.php create mode 100644 resources/views/partials/entity-list-basic.blade.php diff --git a/app/Entities/Book.php b/app/Entities/Book.php index 3bce3860c..e722c4b10 100644 --- a/app/Entities/Book.php +++ b/app/Entities/Book.php @@ -69,6 +69,15 @@ class Book extends Entity return $this->hasMany(Page::class); } + /** + * Get the direct child pages of this book. + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function directPages() + { + return $this->pages()->where('chapter_id', '=', '0'); + } + /** * Get all chapters within this book. * @return \Illuminate\Database\Eloquent\Relations\HasMany diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index e5fd35407..dd9ea8ebf 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -341,6 +341,18 @@ class EntityRepo return $this->permissionService->enforceEntityRestrictions('book', $bookshelf->books())->get(); } + /** + * Get the direct children of a book. + * @param Book $book + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getBookDirectChildren(Book $book) + { + $pages = $this->permissionService->enforceEntityRestrictions('page', $book->directPages())->get(); + $chapters = $this->permissionService->enforceEntityRestrictions('chapters', $book->chapters())->get(); + return collect()->concat($pages)->concat($chapters)->sortBy('priority')->sortByDesc('draft'); + } + /** * Get all child objects of a book. * Returns a sorted collection of Pages and Chapters. diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index d8f2dc4d7..1f4224c79 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -3,6 +3,7 @@ use BookStack\Actions\ViewService; use BookStack\Entities\Repos\EntityRepo; use BookStack\Entities\SearchService; +use BookStack\Exceptions\NotFoundException; use Illuminate\Http\Request; class SearchController extends Controller @@ -104,4 +105,45 @@ class SearchController extends Controller return view('search/entity-ajax-list', ['entities' => $entities]); } + + /** + * Search siblings items in the system. + * @param Request $request + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|mixed + */ + public function searchSiblings(Request $request) + { + $type = $request->get('entity_type', null); + $id = $request->get('entity_id', null); + + $entity = $this->entityRepo->getById($type, $id); + if (!$entity) { + return $this->jsonError(trans('errors.entity_not_found'), 404); + } + + $entities = []; + + // Page in chapter + if ($entity->isA('page') && $entity->chapter) { + $entities = $this->entityRepo->getChapterChildren($entity->chapter); + } + + // Page in book or chapter + if (($entity->isA('page') && !$entity->chapter) || $entity->isA('chapter')) { + $entities = $this->entityRepo->getBookDirectChildren($entity->book); + } + + // Book in shelf + // TODO - When shelve tracking added, Update below if criteria + + // Book + if ($entity->isA('book')) { + $entities = $this->entityRepo->getAll('book'); + } + + // Shelve + // TODO - When shelve tracking added + + return view('partials.entity-list-basic', ['entities' => $entities, 'style' => 'compact']); + } } diff --git a/resources/assets/icons/books.svg b/resources/assets/icons/books.svg new file mode 100644 index 000000000..240201fb3 --- /dev/null +++ b/resources/assets/icons/books.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/resources/assets/js/components/breadcrumb-listing.js b/resources/assets/js/components/breadcrumb-listing.js new file mode 100644 index 000000000..e4f4e5302 --- /dev/null +++ b/resources/assets/js/components/breadcrumb-listing.js @@ -0,0 +1,60 @@ + + +class BreadcrumbListing { + + constructor(elem) { + this.elem = elem; + this.searchInput = elem.querySelector('input'); + this.loadingElem = elem.querySelector('.loading-container'); + this.entityListElem = elem.querySelector('.breadcrumb-listing-entity-list'); + this.toggleElem = elem.querySelector('[dropdown-toggle]'); + + // this.loadingElem.style.display = 'none'; + const entityDescriptor = elem.getAttribute('breadcrumb-listing').split(':'); + this.entityType = entityDescriptor[0]; + this.entityId = Number(entityDescriptor[1]); + + this.toggleElem.addEventListener('click', this.onShow.bind(this)); + this.searchInput.addEventListener('input', this.onSearch.bind(this)); + } + + onShow() { + this.loadEntityView(); + } + + 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'; + } + } + + loadEntityView() { + this.toggleLoading(true); + + const params = { + 'entity_id': this.entityId, + 'entity_type': this.entityType, + }; + + window.$http.get('/search/entity/siblings', {params}).then(resp => { + this.entityListElem.innerHTML = resp.data; + }).catch(err => { + console.error(err); + }).then(() => { + this.toggleLoading(false); + this.onSearch(); + }); + } + + toggleLoading(show = false) { + this.loadingElem.style.display = show ? 'block' : 'none'; + } + +} + +export default BreadcrumbListing; \ No newline at end of file diff --git a/resources/assets/js/components/dropdown.js b/resources/assets/js/components/dropdown.js index dda42e868..400ddb576 100644 --- a/resources/assets/js/components/dropdown.js +++ b/resources/assets/js/components/dropdown.js @@ -6,7 +6,7 @@ class DropDown { constructor(elem) { this.container = elem; - this.menu = elem.querySelector('ul'); + this.menu = elem.querySelector('ul, [dropdown-menu]'); this.toggle = elem.querySelector('[dropdown-toggle]'); this.setupListeners(); } diff --git a/resources/assets/js/components/index.js b/resources/assets/js/components/index.js index 37e78e383..e2749797e 100644 --- a/resources/assets/js/components/index.js +++ b/resources/assets/js/components/index.js @@ -21,7 +21,7 @@ import homepageControl from "./homepage-control"; import headerMobileToggle from "./header-mobile-toggle"; import listSortControl from "./list-sort-control"; import triLayout from "./tri-layout"; - +import breadcrumbListing from "./breadcrumb-listing"; const componentMapping = { 'dropdown': dropdown, @@ -47,6 +47,7 @@ const componentMapping = { 'header-mobile-toggle': headerMobileToggle, 'list-sort-control': listSortControl, 'tri-layout': triLayout, + 'breadcrumb-listing': breadcrumbListing, }; window.components = {}; diff --git a/resources/assets/sass/_header.scss b/resources/assets/sass/_header.scss index a81c11e74..e8667e719 100644 --- a/resources/assets/sass/_header.scss +++ b/resources/assets/sass/_header.scss @@ -220,6 +220,50 @@ header .search-box { } } +.breadcrumb-listing { + position: relative; + .breadcrumb-listing-toggle { + padding: 6px; + border: 1px solid transparent; + border-radius: 4px; + &:hover { + border-color: #DDD; + } + } + .svg-icon { + margin-right: 0; + } +} + +.breadcrumb-listing-dropdown { + box-shadow: $bs-med; + overflow: hidden; + min-height: 100px; + width: 240px; + display: none; + position: absolute; + z-index: 80; + right: -$-m; + .breadcrumb-listing-search .svg-icon { + position: absolute; + left: $-s; + top: 11px; + fill: #888; + pointer-events: none; + } + .breadcrumb-listing-entity-list { + max-height: 400px; + overflow-y: scroll; + text-align: left; + } + input { + padding-left: $-xl; + border-radius: 0; + border: 0; + border-bottom: 1px solid #DDD; + } +} + .faded { a, button, span, span > div { color: #666; diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index 539ffef99..1a9afd794 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -340,10 +340,6 @@ span.sep { /** * Icons */ -i { - padding-right: $-xs; -} - .svg-icon { width: 1em; height: 1em; @@ -351,5 +347,6 @@ i { position: relative; bottom: -0.105em; margin-right: $-xs; + pointer-events: none; } diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 4eeba10cf..e106b3704 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -38,13 +38,13 @@
{{ trans('common.actions') }}
+ @include('partials.view-toggle', ['view' => $view, 'type' => 'book']) @if($currentUser->can('book-create-all')) @icon('add') {{ trans('entities.books_create') }} @endif - @include('partials.view-toggle', ['view' => $view, 'type' => 'book'])
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index d29954228..28983b22c 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -8,6 +8,12 @@ @section('body') +
+ @include('partials.breadcrumbs', ['crumbs' => [ + $book, + ]]) +
+

{{$book->name}}

diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php index 75e4a3be0..f1661a146 100644 --- a/resources/views/common/header.blade.php +++ b/resources/views/common/header.blade.php @@ -30,7 +30,7 @@ @if(userCan('bookshelf-view-all') || userCan('bookshelf-view-own')) @icon('bookshelf'){{ trans('entities.shelves') }} @endif - @icon('book'){{ trans('entities.books') }} + @icon('books'){{ trans('entities.books') }} @if(signedInUser() && userCan('settings-manage')) @icon('settings'){{ trans('settings.settings') }} @endif diff --git a/resources/views/partials/breadcrumb-listing.blade.php b/resources/views/partials/breadcrumb-listing.blade.php new file mode 100644 index 000000000..3dea32023 --- /dev/null +++ b/resources/views/partials/breadcrumb-listing.blade.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/resources/views/partials/breadcrumbs.blade.php b/resources/views/partials/breadcrumbs.blade.php index d9c1b4681..890a6783e 100644 --- a/resources/views/partials/breadcrumbs.blade.php +++ b/resources/views/partials/breadcrumbs.blade.php @@ -1,10 +1,22 @@