diff --git a/core/AppInfo/ConfigLexicon.php b/core/AppInfo/ConfigLexicon.php index c0ebd3b53bf..4fd4eb490ba 100644 --- a/core/AppInfo/ConfigLexicon.php +++ b/core/AppInfo/ConfigLexicon.php @@ -33,6 +33,8 @@ class ConfigLexicon implements ILexicon { public const USER_LOCALE = 'locale'; public const USER_TIMEZONE = 'timezone'; + public const UNIFIED_SEARCH_MIN_SEARCH_LENGTH = 'unified_search_min_search_length'; + public const LASTCRON_TIMESTAMP = 'lastcron'; public function getStrictness(): Strictness { @@ -90,6 +92,7 @@ class ConfigLexicon implements ILexicon { new Entry(self::LASTCRON_TIMESTAMP, ValueType::INT, 0, 'timestamp of last cron execution'), new Entry(self::OCM_DISCOVERY_ENABLED, ValueType::BOOL, true, 'enable/disable OCM', lazy: true), new Entry(self::OCM_INVITE_ACCEPT_DIALOG, ValueType::STRING, '', 'route to local invite accept dialog', lazy: true, note: 'set as empty string to disable feature'), + new Entry(self::UNIFIED_SEARCH_MIN_SEARCH_LENGTH, ValueType::INT, 1, 'Minimum search length to trigger the request', lazy: false, rename: 'unified-search.min-search-length'), ]; } diff --git a/core/Controller/UnifiedSearchController.php b/core/Controller/UnifiedSearchController.php index c770c6240df..169c1f348dc 100644 --- a/core/Controller/UnifiedSearchController.php +++ b/core/Controller/UnifiedSearchController.php @@ -19,6 +19,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; +use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUserSession; @@ -37,6 +38,7 @@ class UnifiedSearchController extends OCSController { private SearchComposer $composer, private IRouter $router, private IURLGenerator $urlGenerator, + private IL10N $l10n, ) { parent::__construct('core', $request); } @@ -101,6 +103,11 @@ class UnifiedSearchController extends OCSController { } catch (UnsupportedFilter|InvalidArgumentException $e) { return new DataResponse($e->getMessage(), Http::STATUS_BAD_REQUEST); } + + if ($filters->count() === 0) { + return new DataResponse($this->l10n->t('No valid filters provided'), Http::STATUS_BAD_REQUEST); + } + return new DataResponse( $this->composer->search( $this->userSession->getUser(), diff --git a/core/src/components/UnifiedSearch/UnifiedSearchModal.vue b/core/src/components/UnifiedSearch/UnifiedSearchModal.vue index 1f92a17269c..d799a797de2 100644 --- a/core/src/components/UnifiedSearch/UnifiedSearchModal.vue +++ b/core/src/components/UnifiedSearch/UnifiedSearchModal.vue @@ -181,6 +181,7 @@ import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' import NcInputField from '@nextcloud/vue/components/NcInputField' import NcDialog from '@nextcloud/vue/components/NcDialog' import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import { loadState } from '@nextcloud/initial-state' import CustomDateRangeModal from './CustomDateRangeModal.vue' import FilterChip from './SearchFilterChip.vue' @@ -281,6 +282,7 @@ export default defineComponent({ internalIsVisible: this.open, initialized: false, searchExternalResources: false, + minSearchLength: loadState('unified-search', 'min-search-length', 1), } }, @@ -293,6 +295,10 @@ export default defineComponent({ return !this.isEmptySearch && this.results.length === 0 }, + isSearchQueryTooShort() { + return this.searchQuery.length < this.minSearchLength + }, + showEmptyContentInfo() { return this.isEmptySearch || this.hasNoResults }, @@ -301,9 +307,16 @@ export default defineComponent({ if (this.searching && this.hasNoResults) { return t('core', 'Searching …') } - if (this.isEmptySearch) { - return t('core', 'Start typing to search') + + if (this.isSearchQueryTooShort) { + switch (this.minSearchLength) { + case 1: + return t('core', 'Start typing to search') + default: + return t('core', 'Minimum search length is {minSearchLength} characters', { minSearchLength: this.minSearchLength }) + } } + return t('core', 'No matching results') }, @@ -395,7 +408,7 @@ export default defineComponent({ }) }, find(query: string, providersToSearchOverride = null) { - if (query.length === 0) { + if (this.isSearchQueryTooShort) { this.results = [] this.searching = false return diff --git a/core/src/views/UnifiedSearch.vue b/core/src/views/UnifiedSearch.vue index 103e47b0425..5f626a5a780 100644 --- a/core/src/views/UnifiedSearch.vue +++ b/core/src/views/UnifiedSearch.vue @@ -5,6 +5,7 @@