diff --git a/src/bundle/Resources/config/services/components/layout.yaml b/src/bundle/Resources/config/services/components/layout.yaml index 890079d56c..d65518ca14 100644 --- a/src/bundle/Resources/config/services/components/layout.yaml +++ b/src/bundle/Resources/config/services/components/layout.yaml @@ -16,3 +16,11 @@ services: $template: '@@ibexadesign/ui/user_menu.html.twig' tags: - { name: ibexa.admin_ui.component, group: user-menu } + + ibexa.search.autocomplete.content_template: + parent: Ibexa\AdminUi\Component\TabsComponent + arguments: + $template: '@@ibexadesign/ui/global_search_autocomplete_content_template.html.twig' + $groupIdentifier: 'global-search-autocomplete-content' + tags: + - { name: ibexa.admin_ui.component, group: global-search-autocomplete-templates } diff --git a/src/bundle/Resources/config/services/ui_config/common.yaml b/src/bundle/Resources/config/services/ui_config/common.yaml index 8ba39dd7c1..06d8621f62 100644 --- a/src/bundle/Resources/config/services/ui_config/common.yaml +++ b/src/bundle/Resources/config/services/ui_config/common.yaml @@ -127,3 +127,10 @@ services: Ibexa\AdminUi\UI\Config\Provider\CurrentBackOfficePath: tags: - { name: ibexa.admin_ui.config.provider, key: 'backOfficePath' } + + Ibexa\AdminUi\UI\Config\Provider\SuggestionSetting: + arguments: + $minQueryLength: '%ibexa.site_access.config.default.search.suggestion.min_query_length%' + $resultLimit: '%ibexa.site_access.config.default.search.suggestion.result_limit%' + tags: + - { name: ibexa.admin_ui.config.provider, key: 'suggestions' } diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index fa5f99edf9..df33a6d51f 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -21,6 +21,7 @@ const layout = [ path.resolve(__dirname, '../public/js/scripts/helpers/form.validation.helper.js'), path.resolve(__dirname, '../public/js/scripts/helpers/form.error.helper.js'), path.resolve(__dirname, '../public/js/scripts/helpers/system.helper.js'), + path.resolve(__dirname, '../public/js/scripts/helpers/highlight.helper.js'), path.resolve(__dirname, '../public/js/scripts/admin.format.date.js'), path.resolve(__dirname, '../public/js/scripts/core/draggable.js'), path.resolve(__dirname, '../public/js/scripts/core/dropdown.js'), @@ -69,6 +70,8 @@ const layout = [ path.resolve(__dirname, '../public/js/scripts/embedded.item.actions'), path.resolve(__dirname, '../public/js/scripts/widgets/flatpickr.js'), path.resolve(__dirname, '../public/js/scripts/admin.form.tabs.validation.js'), + path.resolve(__dirname, '../public/js/scripts/admin.search.autocomplete.js'), + path.resolve(__dirname, '../public/js/scripts/admin.search.autocomplete.content.js'), ]; const fieldTypes = []; diff --git a/src/bundle/Resources/public/js/scripts/admin.search.autocomplete.content.js b/src/bundle/Resources/public/js/scripts/admin.search.autocomplete.content.js new file mode 100644 index 0000000000..46f99794aa --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/admin.search.autocomplete.content.js @@ -0,0 +1,40 @@ +(function (global, doc, ibexa, Routing) { + const globalSearch = doc.querySelector('.ibexa-global-search'); + + if (!globalSearch) { + return; + } + + const { escapeHTML } = ibexa.helpers.text; + const { highlightText } = ibexa.helpers.highlight; + const { getContentTypeIconUrl, getContentTypeName } = ibexa.helpers.contentType; + const autocompleteListNode = globalSearch.querySelector('.ibexa-global-search__autocomplete-list'); + const autocompleteContentTemplateNode = globalSearch.querySelector('.ibexa-global-search__autocomplete-content-template'); + const renderItem = (result, searchText) => { + const { locationId, contentId, name, contentTypeIdentifier, pathString, parentLocations } = result; + const pathArray = pathString.split('/').filter((id) => id); + + const breadcrumb = pathArray.reduce((total, pathLocationId, index) => { + const parentLocation = parentLocations.find((parent) => parent.locationId === parseInt(pathLocationId, 10)); + + if (parseInt(pathLocationId, 10) === locationId) { + return total; + } + + return index === 0 ? parentLocation.name : `${total} / ${parentLocation.name}`; + }, ''); + + const autocompleteItemTemplate = autocompleteContentTemplateNode.dataset.templateItem; + const autocompleteHighlightTemplate = autocompleteListNode.dataset.templateHighlight; + const renderedTemplate = autocompleteItemTemplate + .replace('{{ contentName }}', highlightText(searchText, name, autocompleteHighlightTemplate)) + .replace('{{ iconHref }}', getContentTypeIconUrl(contentTypeIdentifier)) + .replace('{{ contentTypeName }}', escapeHTML(getContentTypeName(contentTypeIdentifier))) + .replaceAll('{{ contentBreadcrumbs }}', breadcrumb) + .replace('{{ contentHref }}', Routing.generate('ibexa.content.view', { contentId, locationId })); + + return renderedTemplate; + }; + + ibexa.addConfig('autocomplete.renderers.content', renderItem, true); +})(window, document, window.ibexa, window.Routing); diff --git a/src/bundle/Resources/public/js/scripts/admin.search.autocomplete.js b/src/bundle/Resources/public/js/scripts/admin.search.autocomplete.js new file mode 100644 index 0000000000..8f56027d84 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/admin.search.autocomplete.js @@ -0,0 +1,148 @@ +(function (global, doc, ibexa, Routing, Translator) { + const globalSearch = doc.querySelector('.ibexa-global-search'); + const { getJsonFromResponse } = ibexa.helpers.request; + const { showErrorNotification } = ibexa.helpers.notification; + const { minQueryLength, resultLimit } = ibexa.adminUiConfig.suggestions; + + if (!globalSearch) { + return; + } + + const globalSearchInput = globalSearch.querySelector('.ibexa-global-search__input'); + const clearBtn = globalSearch.querySelector(' .ibexa-input-text-wrapper__action-btn--clear'); + const autocompleteNode = globalSearch.querySelector('.ibexa-global-search__autocomplete'); + const autocompleteListNode = globalSearch.querySelector('.ibexa-global-search__autocomplete-list'); + let searchAbortController; + const showResults = (searchText, results) => { + const { renderers } = ibexa.autocomplete; + const fragment = doc.createDocumentFragment(); + + results.forEach((result) => { + const container = doc.createElement('ul'); + const renderer = renderers[result.type]; + + if (!renderer) { + return; + } + + const renderedTemplate = renderer(result, searchText); + + container.insertAdjacentHTML('beforeend', renderedTemplate); + + const listItemNode = container.querySelector('li'); + + fragment.append(listItemNode); + }); + + addClickOutsideEventListener(); + addKeyboardEventListener(); + + autocompleteListNode.innerHTML = ''; + autocompleteListNode.append(fragment); + + window.ibexa.helpers.ellipsis.middle.parse(autocompleteListNode); + + autocompleteNode.classList.remove('ibexa-global-search__autocomplete--hidden'); + autocompleteNode.classList.toggle('ibexa-global-search__autocomplete--results-empty', results.length === 0); + }; + const getAutocompleteList = (searchText) => { + const url = Routing.generate('ibexa.search.suggestion', { query: searchText, limit: resultLimit }); + const request = new Request(url, { + mode: 'same-origin', + credentials: 'same-origin', + }); + + searchAbortController = new AbortController(); + + const { signal } = searchAbortController; + + fetch(request, { signal }) + .then(getJsonFromResponse) + .then(showResults.bind(this, searchText)) + .catch((error) => { + if (error.name === 'AbortError') { + return; + } + + showErrorNotification( + Translator.trans(/*@Desc("Cannot load suggestions")*/ 'autocomplete.request.error', {}, 'ibexa_search'), + ); + }); + }; + const handleTyping = (event) => { + const searchText = event.currentTarget.value.trim(); + + searchAbortController?.abort(); + + if (searchText.length <= minQueryLength) { + hideAutocomplete(); + + return; + } + + getAutocompleteList(searchText); + }; + const handleClickOutside = ({ target }) => { + if (target.closest('.ibexa-global-search')) { + return; + } + + hideAutocomplete(); + }; + const addClickOutsideEventListener = () => { + doc.body.addEventListener('click', handleClickOutside, false); + }; + const removeClickOutsideEventListener = () => { + doc.body.removeEventListener('click', handleClickOutside, false); + }; + const handleKeyboard = ({ code }) => { + const keyboardDispatcher = { + ArrowDown: handleArrowDown, + ArrowUp: handleArrowUp, + }; + + keyboardDispatcher[code]?.(); + }; + const handleArrowDown = () => { + const focusedItemElement = autocompleteListNode.querySelector('.ibexa-global-search__autocomplete-item-link:focus'); + const focusedViewAllElement = autocompleteNode.querySelector('.ibexa-global-search__autocomplete-view-all .ibexa-btn:focus'); + + if (!focusedItemElement && !focusedViewAllElement) { + autocompleteListNode.firstElementChild.firstElementChild.focus(); + + return; + } + + if (focusedItemElement?.parentElement?.nextElementSibling) { + focusedItemElement.parentElement.nextElementSibling.firstElementChild.focus(); + } else { + autocompleteNode.querySelector('.ibexa-global-search__autocomplete-view-all .ibexa-btn').focus(); + } + }; + const handleArrowUp = () => { + const focusedItemElement = autocompleteListNode.querySelector('.ibexa-global-search__autocomplete-item-link:focus'); + const focusedViewAllElement = autocompleteNode.querySelector('.ibexa-global-search__autocomplete-view-all .ibexa-btn:focus'); + + if (focusedViewAllElement) { + autocompleteListNode.lastElementChild.firstElementChild.focus(); + + return; + } + + focusedItemElement?.parentElement?.previousElementSibling.firstElementChild.focus(); + }; + const addKeyboardEventListener = () => { + doc.body.addEventListener('keydown', handleKeyboard, false); + }; + const removeKeyboardEventListener = () => { + doc.body.removeEventListener('keydown', handleKeyboard, false); + }; + const hideAutocomplete = () => { + autocompleteNode.classList.add('ibexa-global-search__autocomplete--hidden'); + removeClickOutsideEventListener(); + removeKeyboardEventListener(); + }; + + globalSearchInput.addEventListener('keyup', handleTyping, false); + clearBtn.addEventListener('click', hideAutocomplete, false); +})(window, document, window.ibexa, window.Routing, window.Translator); diff --git a/src/bundle/Resources/public/js/scripts/admin.search.js b/src/bundle/Resources/public/js/scripts/admin.search.js index a1acdeae2a..c9f05d7917 100644 --- a/src/bundle/Resources/public/js/scripts/admin.search.js +++ b/src/bundle/Resources/public/js/scripts/admin.search.js @@ -1,5 +1,5 @@ (function (global, doc) { - const headerSearchInput = doc.querySelector('.ibexa-main-header__search'); + const headerSearchInput = doc.querySelector('.ibexa-global-search__input'); const headerSearchSubmitBtn = doc.querySelector('.ibexa-main-header .ibexa-input-text-wrapper__action-btn--search'); const searchForm = doc.querySelector('.ibexa-search-form'); const searchInput = doc.querySelector('.ibexa-search-form__search-input'); diff --git a/src/bundle/Resources/public/js/scripts/helpers/content.type.helper.js b/src/bundle/Resources/public/js/scripts/helpers/content.type.helper.js index 8a27e42c17..a748e5aef4 100644 --- a/src/bundle/Resources/public/js/scripts/helpers/content.type.helper.js +++ b/src/bundle/Resources/public/js/scripts/helpers/content.type.helper.js @@ -1,5 +1,6 @@ (function (global, doc, ibexa) { let contentTypesDataMap = null; + let contentTypesDataMapByHref = null; /** * Creates map with content types identifiers as keys for faster lookup @@ -16,6 +17,15 @@ return contentTypeDataMap; }, {}); + const createContentTypeDataMapByHref = () => + Object.values(ibexa.adminUiConfig.contentTypes).reduce((contentTypeDataMapByHref, contentTypeGroup) => { + for (const contentTypeData of contentTypeGroup) { + contentTypeDataMapByHref[contentTypeData.href] = contentTypeData; + } + + return contentTypeDataMapByHref; + }, {}); + /** * Returns an URL to a content type icon * @@ -56,8 +66,36 @@ return contentTypesDataMap[contentTypeIdentifier].name; }; + const getContentTypeIconUrlByHref = (contentTypeHref) => { + if (!contentTypesDataMapByHref) { + contentTypesDataMapByHref = createContentTypeDataMapByHref(); + } + + if (!contentTypeHref || !contentTypesDataMapByHref[contentTypeHref]) { + return null; + } + + const iconUrl = contentTypesDataMapByHref[contentTypeHref].thumbnail; + + return iconUrl; + }; + + const getContentTypeNameByHref = (contentTypeHref) => { + if (!contentTypesDataMapByHref) { + contentTypesDataMapByHref = createContentTypeDataMapByHref(); + } + + if (!contentTypeHref || !contentTypesDataMapByHref[contentTypeHref]) { + return null; + } + + return contentTypesDataMapByHref[contentTypeHref].name; + }; + ibexa.addConfig('helpers.contentType', { getContentTypeIconUrl, getContentTypeName, + getContentTypeIconUrlByHref, + getContentTypeNameByHref, }); })(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/js/scripts/helpers/highlight.helper.js b/src/bundle/Resources/public/js/scripts/helpers/highlight.helper.js new file mode 100644 index 0000000000..c3a11812cc --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/helpers/highlight.helper.js @@ -0,0 +1,28 @@ +(function (global, doc, ibexa) { + const { escapeHTML } = ibexa.helpers.text; + const highlightText = (searchText, string, template) => { + const stringLowerCase = string.toLowerCase(); + const searchTextLowerCase = searchText.toLowerCase(); + const matches = stringLowerCase.matchAll(searchTextLowerCase); + const stringArray = []; + let previousIndex = 0; + + for (const match of matches) { + const endOfSearchTextIndex = match.index + searchText.length; + const renderedTemplate = template.replace('{{ highlightText }}', escapeHTML(string.slice(match.index, endOfSearchTextIndex))); + + stringArray.push(escapeHTML(string.slice(previousIndex, match.index))); + stringArray.push(renderedTemplate); + + previousIndex = match.index + searchText.length; + } + + stringArray.push(escapeHTML(string.slice(previousIndex))); + + return stringArray.join(''); + }; + + ibexa.addConfig('helpers.highlight', { + highlightText, + }); +})(window, window.document, window.ibexa); diff --git a/src/bundle/Resources/public/scss/_filters.scss b/src/bundle/Resources/public/scss/_filters.scss index 826f5f6ebb..7a0c5e047b 100644 --- a/src/bundle/Resources/public/scss/_filters.scss +++ b/src/bundle/Resources/public/scss/_filters.scss @@ -49,7 +49,6 @@ .ibexa-label { font-size: $ibexa-text-font-size-medium; - font-weight: 600; color: $ibexa-color-dark; margin-top: calculateRem(16px); margin-bottom: calculateRem(4px); diff --git a/src/bundle/Resources/public/scss/_global-search.scss b/src/bundle/Resources/public/scss/_global-search.scss new file mode 100644 index 0000000000..898fda966f --- /dev/null +++ b/src/bundle/Resources/public/scss/_global-search.scss @@ -0,0 +1,220 @@ +.ibexa-global-search { + width: 100%; + max-width: calculateRem(670px); + position: relative; + + .ibexa-input-text-wrapper { + &--search { + width: auto; + + .ibexa-icon { + fill: $ibexa-color-light; + } + } + + &__action-btn { + &:hover { + .ibexa-icon { + fill: $ibexa-color-info; + } + } + + &--search { + background: $ibexa-color-info-600; + width: calculateRem(58px); + height: calculateRem(48px); + transform: translateX(10px); + + &:hover { + box-shadow: 0 calculateRem(22px) calculateRem(24px) 0 rgba($ibexa-color-info, 0.2); + + .ibexa-icon { + fill: $ibexa-color-white; + } + } + } + } + } + + .ibexa-input--text:not(:focus):placeholder-shown + .ibexa-input-text-wrapper__actions .ibexa-input-text-wrapper__action-btn--search { + background-color: transparent; + + &:hover { + box-shadow: none; + + .ibexa-icon { + fill: $ibexa-color-info; + } + } + } + + &__input { + height: calculateRem(48px); + color: $ibexa-color-white; + border-color: $ibexa-color-info; + background: $ibexa-color-info-800; + + &.ibexa-input--text:not(:focus):placeholder-shown { + border-color: transparent; + } + + &.ibexa-input--text { + &:not(:disabled) { + &:focus, + &:hover { + border-color: $ibexa-color-info; + background: $ibexa-color-info-800; + } + } + } + + &.form-control:focus { + color: $ibexa-color-light; + } + + &::placeholder { + color: $ibexa-color-light; + } + } + + &__autocomplete-no-results { + display: none; + font-size: $ibexa-text-font-size-small; + color: $ibexa-color-dark-400; + padding: calculateRem(8px) 0; + } + + &__autocomplete-view-all { + display: flex; + justify-content: end; + padding: calculateRem(4px) 0; + + .ibexa-btn { + &:focus { + color: $ibexa-color-dark; + } + } + } + + &__autocomplete-item { + padding: calculateRem(4px) 0; + border-bottom: calculateRem(1px) solid $ibexa-color-light; + } + + &__autocomplete-highlight { + position: relative; + + &::before { + content: ''; + background-color: $ibexa-color-info-200; + width: calc(100% + #{calculateRem(4px)}); + height: 100%; + display: block; + position: absolute; + top: 0; + left: calculateRem(-2px); + z-index: -1; + padding: 0 calculateRem(2px); + border-radius: calculateRem(8px); + } + } + + &__autocomplete-item-link { + text-decoration: none; + width: 100%; + display: inline-block; + padding: calculateRem(8px) calculateRem(4px); + border: calculateRem(1px) solid transparent; + border-radius: $ibexa-border-radius; + + &:focus { + box-shadow: 0 0 0 calculateRem(4px) rgba($ibexa-color-primary, 0.2); + border-color: $ibexa-color-primary; + color: $ibexa-color-dark; + } + + &:hover { + text-decoration: none; + color: $ibexa-color-primary; + + .ibexa-global-search__autocomplete-item-info { + color: $ibexa-color-primary; + + .ibexa-middle-ellipsis__name-ellipsized { + color: $ibexa-color-primary; + } + + .ibexa-icon { + fill: $ibexa-color-primary; + } + } + } + } + + &__autocomplete-item-name { + line-height: calculateRem(21px); + margin-bottom: calculateRem(4px); + } + + &__autocomplete-item-info { + font-size: $ibexa-text-font-size-small; + display: flex; + color: $ibexa-color-dark-400; + } + + &__autocomplete-item-content-type-wrapper { + margin-right: calculateRem(4px); + padding-right: calculateRem(4px); + border-right: calculateRem(1px) solid $ibexa-color-light; + font-weight: bold; + display: flex; + justify-content: center; + align-items: center; + white-space: nowrap; + + .ibexa-icon { + margin-right: calculateRem(4px); + } + } + + &__autocomplete-item-breadcrumbs { + min-width: 0; + } + + &__autocomplete-list { + padding-left: 0; + margin-bottom: calculateRem(8px); + list-style: none; + font-size: $ibexa-text-font-size-medium; + } + + &__autocomplete { + position: absolute; + background-color: $ibexa-color-white; + padding: calculateRem(8px); + width: 100%; + top: calc(100% + #{calculateRem(4px)}); + left: 0; + border-radius: $ibexa-border-radius; + z-index: 1050; + border: calculateRem(1px) solid $ibexa-color-light; + box-shadow: calculateRem(4px) calculateRem(32px) calculateRem(47px) 0px rgba($ibexa-color-info-600, 0.1); + + &--hidden { + display: none; + } + + &--results-empty { + .ibexa-global-search { + &__autocomplete-list, + &__autocomplete-view-all { + display: none; + } + + &__autocomplete-no-results { + display: block; + } + } + } + } +} diff --git a/src/bundle/Resources/public/scss/_main-header.scss b/src/bundle/Resources/public/scss/_main-header.scss index 64bda6730b..0be25fdfde 100644 --- a/src/bundle/Resources/public/scss/_main-header.scss +++ b/src/bundle/Resources/public/scss/_main-header.scss @@ -25,71 +25,6 @@ flex: 3; display: flex; justify-content: center; - - form { - width: 100%; - max-width: calculateRem(670px); - - .ibexa-input-text-wrapper--search { - width: auto; - - .ibexa-icon { - fill: $ibexa-color-light; - } - } - } - } - - &__search { - height: calculateRem(48px); - color: $ibexa-color-white; - border-color: $ibexa-color-info; - background: $ibexa-color-info-800; - - &.ibexa-input--text:not(:focus):placeholder-shown { - border-color: transparent; - } - - &.ibexa-input--text { - &:not(:disabled) { - &:focus, - &:hover { - border-color: $ibexa-color-info; - background: $ibexa-color-info-800; - } - } - } - - &.form-control:focus { - color: $ibexa-color-light; - } - - &::placeholder { - color: $ibexa-color-light; - } - } - - .ibexa-input-text-wrapper__action-btn { - &:hover { - .ibexa-icon { - fill: $ibexa-color-info; - } - } - - &--search { - background: $ibexa-color-info-600; - width: calculateRem(58px); - height: calculateRem(48px); - transform: translateX(10px); - - &:hover { - box-shadow: 0 calculateRem(22px) calculateRem(24px) 0 rgba($ibexa-color-info, 0.2); - - .ibexa-icon { - fill: $ibexa-color-white; - } - } - } } &__user-menu-column { @@ -98,16 +33,4 @@ align-items: center; justify-content: flex-end; } - - .ibexa-input--text:not(:focus):placeholder-shown + .ibexa-input-text-wrapper__actions .ibexa-input-text-wrapper__action-btn--search { - background-color: transparent; - - &:hover { - box-shadow: none; - - .ibexa-icon { - fill: $ibexa-color-info; - } - } - } } diff --git a/src/bundle/Resources/public/scss/_search-form.scss b/src/bundle/Resources/public/scss/_search-form.scss index 881a463290..a828f1f666 100644 --- a/src/bundle/Resources/public/scss/_search-form.scss +++ b/src/bundle/Resources/public/scss/_search-form.scss @@ -2,11 +2,12 @@ display: grid; grid-template-areas: 'search search' - 'filters results' - '. results'; - grid-template-columns: calculateRem(270px) calc(100% - #{calculateRem(294px)}); + 'results filters' + 'results .'; + grid-template-columns: calc(100% - #{calculateRem(294px)}) calculateRem(270px); grid-column-gap: calculateRem(24px); padding: calculateRem(16px); + overflow: hidden; &__search-wrapper { display: flex; diff --git a/src/bundle/Resources/public/scss/ibexa.scss b/src/bundle/Resources/public/scss/ibexa.scss index d0d6278233..2c67508511 100644 --- a/src/bundle/Resources/public/scss/ibexa.scss +++ b/src/bundle/Resources/public/scss/ibexa.scss @@ -125,4 +125,5 @@ @import 'summary-tile'; @import 'double-input-range'; @import 'switcher'; +@import 'global-search'; @import 'user-name'; diff --git a/src/bundle/Resources/translations/ibexa_search.en.xliff b/src/bundle/Resources/translations/ibexa_search.en.xliff index 5a797913c1..bb849c2243 100644 --- a/src/bundle/Resources/translations/ibexa_search.en.xliff +++ b/src/bundle/Resources/translations/ibexa_search.en.xliff @@ -6,6 +6,26 @@ The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. + + No suggestions available. + No suggestions available. + key: autocomplete.no_results + + + Cannot load suggestions + Cannot load suggestions + key: autocomplete.request.error + + + View all results + View all results + key: autocomplete.view_all_results + + + Search... + Search... + key: header.search + Search Search diff --git a/src/bundle/Resources/translations/messages.en.xliff b/src/bundle/Resources/translations/messages.en.xliff index f6516781c9..71c6d86ea9 100644 --- a/src/bundle/Resources/translations/messages.en.xliff +++ b/src/bundle/Resources/translations/messages.en.xliff @@ -483,11 +483,6 @@ Cancel key: form.cancel - - Search... - Search... - key: header.search - Reset your password Reset your password diff --git a/src/bundle/Resources/views/themes/admin/ui/global_search.html.twig b/src/bundle/Resources/views/themes/admin/ui/global_search.html.twig index 8e7796aeb3..69e461413c 100644 --- a/src/bundle/Resources/views/themes/admin/ui/global_search.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/global_search.html.twig @@ -1,10 +1,36 @@ {% form_theme form '@ibexadesign/ui/form_fields.html.twig' %} -{{ form_start(form, { attr: { class: 'form-inline' }}) }} - {{ form_widget(form.query, { - attr: { - class: 'ibexa-main-header__search', - placeholder: 'header.search'|trans|desc('Search...') - } - }) }} -{{ form_end(form) }} +{% trans_default_domain 'ibexa_search' %} + + diff --git a/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_item.html.twig b/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_item.html.twig new file mode 100644 index 0000000000..f4a8b9f189 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_item.html.twig @@ -0,0 +1,23 @@ +
  • + +
    + {{ content_name }} +
    +
    +
    + + + + + {{ content_type_name }} + +
    +
    + {{ include('@ibexadesign/ui/component/middle_ellipsis/middle_ellipsis.html.twig', { + name: content_breadcrumbs, + }) }} +
    +
    + +
    +
  • diff --git a/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_template.html.twig b/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_template.html.twig new file mode 100644 index 0000000000..26a411521d --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_content_template.html.twig @@ -0,0 +1,10 @@ +
    +
    diff --git a/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_highlight.html.twig b/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_highlight.html.twig new file mode 100644 index 0000000000..feb829b976 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/ui/global_search_autocomplete_highlight.html.twig @@ -0,0 +1 @@ +{{ highlight_text }} diff --git a/src/bundle/Resources/views/themes/admin/ui/search/form.html.twig b/src/bundle/Resources/views/themes/admin/ui/search/form.html.twig index ce960a8452..bee3294587 100644 --- a/src/bundle/Resources/views/themes/admin/ui/search/form.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/search/form.html.twig @@ -21,6 +21,6 @@ {{ form_widget(form.created, {'attr': {'hidden': 'hidden'}}) }} {{ form_widget(form.creator, {'attr': {'hidden': 'hidden'}}) }} - {% include '@ibexadesign/ui/search/filters.html.twig' %} {% include '@ibexadesign/ui/search/results.html.twig' %} + {% include '@ibexadesign/ui/search/filters.html.twig' %} {{ form_end(form) }} diff --git a/src/lib/UI/Config/Provider/SuggestionSetting.php b/src/lib/UI/Config/Provider/SuggestionSetting.php new file mode 100644 index 0000000000..33df81827b --- /dev/null +++ b/src/lib/UI/Config/Provider/SuggestionSetting.php @@ -0,0 +1,35 @@ +minQueryLength = $minQueryLength; + $this->resultLimit = $resultLimit; + } + + /** + * @return array + */ + public function getConfig(): array + { + return [ + 'minQueryLength' => $this->minQueryLength, + 'resultLimit' => $this->resultLimit, + ]; + } +}