diff --git a/app/Actions/ViewService.php b/app/Actions/ViewService.php index d5f8002fc..292784e86 100644 --- a/app/Actions/ViewService.php +++ b/app/Actions/ViewService.php @@ -2,21 +2,26 @@ use BookStack\Auth\Permissions\PermissionService; use BookStack\Entities\Entity; +use BookStack\Entities\EntityProvider; +use Illuminate\Support\Collection; class ViewService { protected $view; protected $permissionService; + protected $entityProvider; /** * ViewService constructor. * @param \BookStack\Actions\View $view * @param \BookStack\Auth\Permissions\PermissionService $permissionService + * @param EntityProvider $entityProvider */ - public function __construct(View $view, PermissionService $permissionService) + public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider) { $this->view = $view; $this->permissionService = $permissionService; + $this->entityProvider = $entityProvider; } /** @@ -50,11 +55,11 @@ class ViewService * Get the entities with the most views. * @param int $count * @param int $page - * @param Entity|false|array $filterModel + * @param string|array $filterModels * @param string $action - used for permission checking - * @return + * @return Collection */ - public function getPopular($count = 10, $page = 0, $filterModel = false, $action = 'view') + public function getPopular(int $count = 10, int $page = 0, $filterModels = null, $action = 'view') { // TODO - Standardise input filter $skipCount = $count * $page; @@ -63,10 +68,8 @@ class ViewService ->groupBy('viewable_id', 'viewable_type') ->orderBy('view_count', 'desc'); - if ($filterModel && is_array($filterModel)) { - $query->whereIn('viewable_type', $filterModel); - } else if ($filterModel) { - $query->where('viewable_type', '=', $filterModel->getMorphClass()); + if ($filterModels) { + $query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels)); } return $query->with('viewable')->skip($skipCount)->take($count)->get()->pluck('viewable'); diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 8fc70e916..1e1ee3946 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -704,7 +704,7 @@ class PermissionService * @param string $entityIdColumn * @param string $entityTypeColumn * @param string $action - * @return mixed + * @return QueryBuilder */ public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view') { diff --git a/app/Entities/EntityProvider.php b/app/Entities/EntityProvider.php index 04939a14a..d0d4a7ad6 100644 --- a/app/Entities/EntityProvider.php +++ b/app/Entities/EntityProvider.php @@ -84,4 +84,23 @@ class EntityProvider $type = strtolower($type); return $this->all()[$type]; } + + /** + * Get the morph classes, as an array, for a single or multiple types. + * @param string|array $types + * @return array + */ + public function getMorphClasses($types) + { + if (is_string($types)) { + $types = [$types]; + } + + $morphClasses = []; + foreach ($types as $type) { + $model = $this->get($type); + $morphClasses[] = $model->getMorphClass(); + } + return $morphClasses; + } } diff --git a/app/Entities/Repos/EntityRepo.php b/app/Entities/Repos/EntityRepo.php index dd9ea8ebf..6fc2689a5 100644 --- a/app/Entities/Repos/EntityRepo.php +++ b/app/Entities/Repos/EntityRepo.php @@ -293,15 +293,14 @@ class EntityRepo /** * Get the most popular entities base on all views. - * @param string|bool $type + * @param string $type * @param int $count * @param int $page * @return mixed */ - public function getPopular($type, $count = 10, $page = 0) + public function getPopular(string $type, int $count = 10, int $page = 0) { - $filter = is_bool($type) ? false : $this->entityProvider->get($type); - return $this->viewService->getPopular($count, $page, $filter); + return $this->viewService->getPopular($count, $page, $type); } /** diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 1f4224c79..8ce5aaa74 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -88,22 +88,19 @@ class SearchController extends Controller */ public function searchEntitiesAjax(Request $request) { - $entityTypes = $request->filled('types') ? collect(explode(',', $request->get('types'))) : collect(['page', 'chapter', 'book']); + $entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book']; $searchTerm = $request->get('term', false); $permission = $request->get('permission', 'view'); // Search for entities otherwise show most popular if ($searchTerm !== false) { - $searchTerm .= ' {type:'. implode('|', $entityTypes->toArray()) .'}'; + $searchTerm .= ' {type:'. implode('|', $entityTypes) .'}'; $entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results']; } else { - $entityNames = $entityTypes->map(function ($type) { - return 'BookStack\\' . ucfirst($type); // TODO - Extract this elsewhere, too specific and stringy - })->toArray(); - $entities = $this->viewService->getPopular(20, 0, $entityNames, $permission); + $entities = $this->viewService->getPopular(20, 0, $entityTypes, $permission); } - return view('search/entity-ajax-list', ['entities' => $entities]); + return view('search.entity-ajax-list', ['entities' => $entities]); } /** diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php index 5508ee9cd..e7bde5290 100644 --- a/app/Providers/CustomFacadeProvider.php +++ b/app/Providers/CustomFacadeProvider.php @@ -2,20 +2,11 @@ namespace BookStack\Providers; -use BookStack\Actions\Activity; use BookStack\Actions\ActivityService; -use BookStack\Actions\View; use BookStack\Actions\ViewService; -use BookStack\Auth\Permissions\PermissionService; -use BookStack\Settings\Setting; use BookStack\Settings\SettingService; -use BookStack\Uploads\HttpFetcher; -use BookStack\Uploads\Image; use BookStack\Uploads\ImageService; -use Illuminate\Contracts\Cache\Repository; -use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Support\ServiceProvider; -use Intervention\Image\ImageManager; class CustomFacadeProvider extends ServiceProvider { @@ -37,34 +28,19 @@ class CustomFacadeProvider extends ServiceProvider public function register() { $this->app->bind('activity', function () { - return new ActivityService( - $this->app->make(Activity::class), - $this->app->make(PermissionService::class) - ); + return $this->app->make(ActivityService::class); }); $this->app->bind('views', function () { - return new ViewService( - $this->app->make(View::class), - $this->app->make(PermissionService::class) - ); + return $this->app->make(ViewService::class); }); $this->app->bind('setting', function () { - return new SettingService( - $this->app->make(Setting::class), - $this->app->make(Repository::class) - ); + return $this->app->make(SettingService::class); }); $this->app->bind('images', function () { - return new ImageService( - $this->app->make(Image::class), - $this->app->make(ImageManager::class), - $this->app->make(Factory::class), - $this->app->make(Repository::class), - $this->app->make(HttpFetcher::class) - ); + return $this->app->make(ImageService::class); }); } } diff --git a/resources/assets/js/components/entity-selector.js b/resources/assets/js/components/entity-selector.js index 461bf7321..a72578af6 100644 --- a/resources/assets/js/components/entity-selector.js +++ b/resources/assets/js/components/entity-selector.js @@ -6,8 +6,8 @@ class EntitySelector { this.search = ''; this.lastClick = 0; - let entityTypes = elem.hasAttribute('entity-types') ? elem.getAttribute('entity-types') : 'page,book,chapter'; - let entityPermission = elem.hasAttribute('entity-permission') ? elem.getAttribute('entity-permission') : 'view'; + const entityTypes = elem.hasAttribute('entity-types') ? elem.getAttribute('entity-types') : 'page,book,chapter'; + const entityPermission = elem.hasAttribute('entity-permission') ? elem.getAttribute('entity-permission') : 'view'; this.searchUrl = window.baseUrl(`/ajax/search/entities?types=${encodeURIComponent(entityTypes)}&permission=${encodeURIComponent(entityPermission)}`); this.input = elem.querySelector('[entity-selector-input]'); @@ -26,6 +26,7 @@ class EntitySelector { this.searchEntities(this.searchInput.value); }, 200); }); + this.searchInput.addEventListener('keydown', event => { if (event.keyCode === 13) event.preventDefault(); }); @@ -53,7 +54,7 @@ class EntitySelector { searchEntities(searchTerm) { this.input.value = ''; - let url = this.searchUrl + `&term=${encodeURIComponent(searchTerm)}`; + let url = `${this.searchUrl}&term=${encodeURIComponent(searchTerm)}`; window.$http.get(url).then(resp => { this.resultsContainer.innerHTML = resp.data; this.hideLoading(); @@ -68,48 +69,47 @@ class EntitySelector { } onClick(event) { - let t = event.target; - - if (t.matches('.entity-list-item *')) { + const listItem = event.target.closest('[data-entity-type]'); + if (listItem) { event.preventDefault(); event.stopPropagation(); - let item = t.closest('[data-entity-type]'); - this.selectItem(item); - } else if (t.matches('[data-entity-type]')) { - this.selectItem(t) + this.selectItem(listItem); } - } selectItem(item) { - let isDblClick = this.isDoubleClick(); - let type = item.getAttribute('data-entity-type'); - let id = item.getAttribute('data-entity-id'); - let isSelected = !item.classList.contains('selected') || isDblClick; + const isDblClick = this.isDoubleClick(); + const type = item.getAttribute('data-entity-type'); + const id = item.getAttribute('data-entity-id'); + const isSelected = (!item.classList.contains('selected') || isDblClick); this.unselectAll(); this.input.value = isSelected ? `${type}:${id}` : ''; - if (!isSelected) window.$events.emit('entity-select-change', null); if (isSelected) { item.classList.add('selected'); - item.classList.add('primary-background'); + } else { + window.$events.emit('entity-select-change', null) } + if (!isDblClick && !isSelected) return; - let link = item.querySelector('.entity-list-item-link').getAttribute('href'); - let name = item.querySelector('.entity-list-item-name').textContent; - let data = {id: Number(id), name: name, link: link}; + const link = item.getAttribute('href'); + const name = item.querySelector('.entity-list-item-name').textContent; + const data = {id: Number(id), name: name, link: link}; - if (isDblClick) window.$events.emit('entity-select-confirm', data); - if (isSelected) window.$events.emit('entity-select-change', data); + if (isDblClick) { + window.$events.emit('entity-select-confirm', data) + } + if (isSelected) { + window.$events.emit('entity-select-change', data) + } } unselectAll() { let selected = this.elem.querySelectorAll('.selected'); - for (let i = 0, len = selected.length; i < len; i++) { - selected[i].classList.remove('selected'); - selected[i].classList.remove('primary-background'); + for (let selectedElem of selected) { + selectedElem.classList.remove('selected', 'primary-background'); } } diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index ad0b90e7b..5e3c2e45c 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -198,10 +198,10 @@ border-radius: 1px; opacity: 0.6; } - .entity-list-item .icon:after { + .entity-list-item .icon:after { opacity: 1; } - .entity-list-item .icon svg { + .entity-list-item .icon svg { display: none; } } @@ -399,6 +399,15 @@ ul.pagination { } } +.entity-list-item-path-sep { + display: inline-block; + vertical-align: top; + position: relative; + top: 1px; + svg { + margin-right: 0; + } +} .card .entity-list-item:not(.no-hover):hover { background-color: #F2F2F2; diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index df1132de5..d5da11f3e 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -187,23 +187,22 @@ $btt-size: 40px; overflow-y: scroll; height: 400px; background-color: #EEEEEE; + margin-right: 0; + margin-left: 0; + } + .entity-list-item { + background-color: #FFF; + } + .entity-list-item p { + margin-bottom: 0; + } + .entity-list-item.selected { + background-color: rgba(0, 0, 0, 0.15) !important; } .loading { height: 400px; padding-top: $-l; } - .entity-list > p { - text-align: center; - padding-top: $-l; - font-size: 1.333em; - } - .entity-list > div { - padding-left: $-m; - padding-right: $-m; - background-color: #FFF; - transition: all ease-in-out 120ms; - cursor: pointer; - } &.compact { font-size: 10px; .entity-item-snippet { diff --git a/resources/views/components/entity-selector.blade.php b/resources/views/components/entity-selector.blade.php index c85bf8fca..89c574c28 100644 --- a/resources/views/components/entity-selector.blade.php +++ b/resources/views/components/entity-selector.blade.php @@ -5,5 +5,4 @@
@include('partials.loading-icon')
- -{{--TODO--}} \ No newline at end of file + \ No newline at end of file diff --git a/resources/views/partials/entity-list-item.blade.php b/resources/views/partials/entity-list-item.blade.php index d971ae0db..d42b1967f 100644 --- a/resources/views/partials/entity-list-item.blade.php +++ b/resources/views/partials/entity-list-item.blade.php @@ -1,5 +1,15 @@ @component('partials.entity-list-item-basic', ['entity' => $entity])
+ + @if($showPath ?? false) + @if($entity->book_id) + {{ $entity->book->getShortName(42) }} + @if($entity->chapter_id) + @icon('chevron-right') {{ $entity->chapter->getShortName(42) }} + @endif + @endif + @endif +

{{ $entity->getExcerpt() }}

@endcomponent \ No newline at end of file diff --git a/resources/views/partials/entity-list.blade.php b/resources/views/partials/entity-list.blade.php index b2a26f1e4..c0f922fdd 100644 --- a/resources/views/partials/entity-list.blade.php +++ b/resources/views/partials/entity-list.blade.php @@ -2,7 +2,7 @@
@if(count($entities) > 0) @foreach($entities as $index => $entity) - @include('partials.entity-list-item', ['entity' => $entity]) + @include('partials.entity-list-item', ['entity' => $entity, 'showPath' => $showPath ?? false]) @endforeach @else

diff --git a/resources/views/search/all.blade.php b/resources/views/search/all.blade.php index f7668b896..fba67abf9 100644 --- a/resources/views/search/all.blade.php +++ b/resources/views/search/all.blade.php @@ -192,7 +192,7 @@

{{ trans('entities.search_results') }}

{{ trans_choice('entities.search_total_results_found', $totalResults, ['count' => $totalResults]) }}
- @include('partials.entity-list', ['entities' => $entities]) + @include('partials.entity-list', ['entities' => $entities, 'showPath' => true])
@if($hasNextPage)
diff --git a/resources/views/search/entity-ajax-list.blade.php b/resources/views/search/entity-ajax-list.blade.php index 1e0b325f9..36a28b93e 100644 --- a/resources/views/search/entity-ajax-list.blade.php +++ b/resources/views/search/entity-ajax-list.blade.php @@ -1,21 +1,15 @@ -
+
@if(count($entities) > 0) @foreach($entities as $index => $entity) - @if($entity->isA('page')) - @include('pages/list-item', ['page' => $entity, 'showPath' => true]) - @elseif($entity->isA('book')) - @include('books/list-item', ['book' => $entity]) - @elseif($entity->isA('chapter')) - @include('chapters/list-item', ['chapter' => $entity, 'hidePages' => true, 'showPath' => true]) - @endif + @include('partials.entity-list-item', ['entity' => $entity, 'showPath' => true]) @if($index !== count($entities) - 1)
@endif @endforeach @else -

+

{{ trans('common.no_items') }}

@endif