diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 699733e37..8fcde4a6d 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -11,7 +11,7 @@ use Illuminate\Http\Request; class SearchController extends Controller { - protected $searchRunner; + protected SearchRunner $searchRunner; public function __construct(SearchRunner $searchRunner) { diff --git a/resources/js/components/global-search.js b/resources/js/components/global-search.js new file mode 100644 index 000000000..b351278d6 --- /dev/null +++ b/resources/js/components/global-search.js @@ -0,0 +1,47 @@ +/** + * @extends {Component} + */ +import {htmlToDom} from "../services/dom"; + +class GlobalSearch { + + setup() { + this.input = this.$refs.input; + this.suggestions = this.$refs.suggestions; + this.suggestionResultsWrap = this.$refs.suggestionResults; + + this.setupListeners(); + } + + setupListeners() { + this.input.addEventListener('input', () => { + const value = this.input.value; + if (value.length > 0) { + this.updateSuggestions(value); + } else { + this.hideSuggestions(); + } + }); + } + + async updateSuggestions(search) { + const {data: results} = await window.$http.get('/ajax/search/entities', {term: search, count: 5}); + const resultDom = htmlToDom(results); + + const childrenToTrim = Array.from(resultDom.children).slice(9); + for (const child of childrenToTrim) { + child.remove(); + } + + this.suggestions.style.display = 'block'; + this.suggestionResultsWrap.innerHTML = ''; + this.suggestionResultsWrap.append(resultDom); + } + + hideSuggestions() { + this.suggestions.style.display = null; + this.suggestionResultsWrap.innerHTML = ''; + } +} + +export default GlobalSearch; \ No newline at end of file diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 9f801668e..41dbf4de5 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -57,6 +57,7 @@ import triLayout from "./tri-layout.js" import userSelect from "./user-select.js" import webhookEvents from "./webhook-events"; import wysiwygEditor from "./wysiwyg-editor.js" +import globalSearch from "./global-search"; const componentMapping = { "add-remove-rows": addRemoveRows, @@ -86,6 +87,7 @@ const componentMapping = { "entity-selector-popup": entitySelectorPopup, "event-emit-select": eventEmitSelect, "expand-toggle": expandToggle, + "global-search": globalSearch, "header-mobile-toggle": headerMobileToggle, "homepage-control": homepageControl, "image-manager": imageManager, diff --git a/resources/sass/_blocks.scss b/resources/sass/_blocks.scss index 6058add82..302e7ed4e 100644 --- a/resources/sass/_blocks.scss +++ b/resources/sass/_blocks.scss @@ -86,11 +86,13 @@ .card-title a { line-height: 1; } -.card-footer-link { +.card-footer-link, button.card-footer-link { display: block; padding: $-s $-m; line-height: 1; border-top: 1px solid; + width: 100%; + text-align: left; @include lightDark(border-color, #DDD, #555); border-radius: 0 0 3px 3px; font-size: 0.9em; diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index 66d76aaa2..31ec6a2c0 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -1010,4 +1010,31 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { border: 1px solid #b4b4b4; box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset; color: #333; +} + +.global-search-suggestions { + display: none; + position: absolute; + top: -$-s; + left: 0; + right: 0; + z-index: -1; + margin-left: -$-xxl; + margin-right: -$-xxl; + padding-top: 56px; + border-radius: 3px; + box-shadow: $bs-hover; + .entity-item-snippet p { + display: none; + } + .entity-item-snippet { + font-size: .8rem; + } + .entity-list-item-name { + font-size: .9rem; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + } } \ No newline at end of file diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 7de8a9d7d..709db3bd6 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -412,7 +412,7 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] { .search-box { max-width: 100%; position: relative; - button { + button[tabindex="-1"] { background-color: transparent; border: none; @include lightDark(color, #666, #AAA); diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index 923f026c2..e0b494e77 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -108,21 +108,6 @@ header .search-box { border: 1px solid rgba(255, 255, 255, 0.4); } } - button { - z-index: 1; - left: 16px; - top: 10px; - color: #FFF; - opacity: 0.6; - @include lightDark(color, rgba(255, 255, 255, 0.8), #AAA); - @include rtl { - left: auto; - right: 16px; - } - svg { - margin-block-end: 0; - } - } input::placeholder { color: #FFF; opacity: 0.6; @@ -130,10 +115,25 @@ header .search-box { @include between($l, $xl) { max-width: 200px; } - &:focus-within button { + &:focus-within #header-search-box-button { opacity: 1; } } +#header-search-box-button { + z-index: 1; + left: 16px; + top: 10px; + color: #FFF; + opacity: 0.6; + @include lightDark(color, rgba(255, 255, 255, 0.8), #AAA); + @include rtl { + left: auto; + right: 16px; + } + svg { + margin-block-end: 0; + } +} .logo { display: inline-flex; diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php index 9fe97b853..1340be26d 100644 --- a/resources/views/common/header.blade.php +++ b/resources/views/common/header.blade.php @@ -19,12 +19,19 @@ <div class="flex-container-column items-center justify-center hide-under-l"> @if (hasAppAccess()) - <form action="{{ url('/search') }}" method="GET" class="search-box" role="search"> + <form component="global-search" action="{{ url('/search') }}" method="GET" class="search-box" role="search"> <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button> - <input id="header-search-box-input" type="text" name="term" + <input id="header-search-box-input" + refs="global-search@input" + type="text" + name="term" data-shortcut="global_search" aria-label="{{ trans('common.search') }}" placeholder="{{ trans('common.search') }}" - value="{{ isset($searchTerm) ? $searchTerm : '' }}"> + value="{{ $searchTerm ?? '' }}"> + <div refs="global-search@suggestions" class="global-search-suggestions card"> + <div refs="global-search@suggestion-results" class="px-m"></div> + <button class="text-button card-footer-link" type="submit">{{ trans('common.view_all') }}</button> + </div> </form> @endif </div>