|
27 | 27 | :label="t('core', 'Search apps, files, tags, messages') + '...'" |
28 | 28 | @update:value="debouncedFind" /> |
29 | 29 | <div class="unified-search-modal__filters" data-cy-unified-search-filters> |
30 | | - <NcActions :menu-name="t('core', 'Places')" :open.sync="providerActionMenuIsOpen" data-cy-unified-search-filter="places"> |
| 30 | + <NcActions :open.sync="providerActionMenuIsOpen" :menu-name="t('core', 'Places')" data-cy-unified-search-filter="places"> |
31 | 31 | <template #icon> |
32 | 32 | <IconListBox :size="20" /> |
33 | 33 | </template> |
|
43 | 43 | {{ provider.name }} |
44 | 44 | </NcActionButton> |
45 | 45 | </NcActions> |
46 | | - <NcActions :menu-name="t('core', 'Date')" :open.sync="dateActionMenuIsOpen" data-cy-unified-search-filter="date"> |
| 46 | + <NcActions :open.sync="dateActionMenuIsOpen" :menu-name="t('core', 'Date')" data-cy-unified-search-filter="date"> |
47 | 47 | <template #icon> |
48 | 48 | <IconCalendarRange :size="20" /> |
49 | 49 | </template> |
|
127 | 127 | <h3 class="hidden-visually"> |
128 | 128 | {{ t('core', 'Results') }} |
129 | 129 | </h3> |
130 | | - <div v-for="providerResult in results" :key="providerResult.id" class="result"> |
| 130 | + <!-- Filtered results section --> |
| 131 | + <div v-for="providerResult in filteredResults" :key="providerResult.id" class="result"> |
131 | 132 | <h4 :id="`unified-search-result-${providerResult.id}`" class="result-title"> |
132 | 133 | {{ providerResult.name }} |
133 | 134 | </h4> |
|
151 | 152 | </NcButton> |
152 | 153 | </div> |
153 | 154 | </div> |
| 155 | + <!-- Unfiltered results section --> |
| 156 | + <template v-if="unfilteredResults.length > 0"> |
| 157 | + <div class="unified-search-modal__unfiltered-header"> |
| 158 | + <span class="unified-search-modal__unfiltered-label">{{ t('core', 'Partial matches') }}</span> |
| 159 | + </div> |
| 160 | + <div v-for="providerResult in unfilteredResults" :key="`unfiltered-${providerResult.id}`" class="result result--unfiltered"> |
| 161 | + <h4 :id="`unified-search-result-unfiltered-${providerResult.id}`" class="result-title"> |
| 162 | + {{ providerResult.name }} |
| 163 | + </h4> |
| 164 | + <ul class="result-items" :aria-labelledby="`unified-search-result-unfiltered-${providerResult.id}`"> |
| 165 | + <SearchResult v-for="(result, index) in providerResult.results" |
| 166 | + :key="index" |
| 167 | + v-bind="result" /> |
| 168 | + </ul> |
| 169 | + <div class="result-footer"> |
| 170 | + <NcButton v-if="providerResult.results.length === providerResult.limit" variant="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult)"> |
| 171 | + {{ t('core', 'Load more results') }} |
| 172 | + <template #icon> |
| 173 | + <IconDotsHorizontal :size="20" /> |
| 174 | + </template> |
| 175 | + </NcButton> |
| 176 | + <NcButton v-if="providerResult.inAppSearch" alignment="end-reverse" variant="tertiary-no-background"> |
| 177 | + {{ t('core', 'Search in') }} {{ providerResult.name }} |
| 178 | + <template #icon> |
| 179 | + <IconArrowRight :size="20" /> |
| 180 | + </template> |
| 181 | + </NcButton> |
| 182 | + </div> |
| 183 | + </div> |
| 184 | + </template> |
154 | 185 | </div> |
155 | 186 | </NcDialog> |
156 | 187 | </template> |
@@ -335,6 +366,50 @@ export default defineComponent({ |
335 | 366 | hasExternalResources() { |
336 | 367 | return this.providers.some(provider => provider.isExternalProvider) |
337 | 368 | }, |
| 369 | +
|
| 370 | + hasContentFilters() { |
| 371 | + return this.filters.some((filter) => filter.type === 'date' || filter.type === 'person') |
| 372 | + }, |
| 373 | +
|
| 374 | + filteredResults() { |
| 375 | + const isInFolderAtRoot = (result) => { |
| 376 | + if (result.id !== 'in-folder') { |
| 377 | + return false |
| 378 | + } |
| 379 | + const path = result.extraParams?.path |
| 380 | + return !path || path === '/' || path === '' |
| 381 | + } |
| 382 | +
|
| 383 | + if (!this.hasContentFilters) { |
| 384 | + return this.results.filter((result) => !isInFolderAtRoot(result)) |
| 385 | + } |
| 386 | + return this.results.filter((result) => result.supportsActiveFilters === true && !isInFolderAtRoot(result)) |
| 387 | + }, |
| 388 | +
|
| 389 | + filteredResultUrls() { |
| 390 | + const urls = new Set() |
| 391 | + this.filteredResults.forEach((provider) => { |
| 392 | + provider.results.forEach((entry) => { |
| 393 | + if (entry.resourceUrl) { |
| 394 | + urls.add(entry.resourceUrl) |
| 395 | + } |
| 396 | + }) |
| 397 | + }) |
| 398 | + return urls |
| 399 | + }, |
| 400 | +
|
| 401 | + unfilteredResults() { |
| 402 | + if (!this.hasContentFilters) { |
| 403 | + return [] |
| 404 | + } |
| 405 | + return this.results |
| 406 | + .filter((result) => result.supportsActiveFilters === false) |
| 407 | + .map((provider) => ({ |
| 408 | + ...provider, |
| 409 | + results: provider.results.filter((entry) => !this.filteredResultUrls.has(entry.resourceUrl)), |
| 410 | + })) |
| 411 | + .filter((provider) => provider.results.length > 0) |
| 412 | + }, |
338 | 413 | }, |
339 | 414 |
|
340 | 415 | watch: { |
@@ -473,6 +548,7 @@ export default defineComponent({ |
473 | 548 | ...provider, |
474 | 549 | results: response.data.ocs.data.entries, |
475 | 550 | limit: params.limit ?? 5, |
| 551 | + supportsActiveFilters, |
476 | 552 | }) |
477 | 553 |
|
478 | 554 | unifiedSearchLogger.debug('Unified search results:', { results: this.results, newResults }) |
@@ -553,10 +629,6 @@ export default defineComponent({ |
553 | 629 | this.filters[existingPersonFilter].name = person.displayName |
554 | 630 | } |
555 | 631 |
|
556 | | - this.providers.forEach(async (provider, index) => { |
557 | | - this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['person'])) |
558 | | - }) |
559 | | -
|
560 | 632 | this.debouncedFind(this.searchQuery) |
561 | 633 | unifiedSearchLogger.debug('Person filter applied', { person }) |
562 | 634 | }, |
@@ -610,7 +682,6 @@ export default defineComponent({ |
610 | 682 | for (let i = 0; i < this.filters.length; i++) { |
611 | 683 | if (this.filters[i].id === filter.id) { |
612 | 684 | this.filters.splice(i, 1) |
613 | | - this.enableAllProviders() |
614 | 685 | break |
615 | 686 | } |
616 | 687 | } |
@@ -649,9 +720,6 @@ export default defineComponent({ |
649 | 720 | this.filters.push(this.dateFilter) |
650 | 721 | } |
651 | 722 |
|
652 | | - this.providers.forEach(async (provider, index) => { |
653 | | - this.providers[index].disabled = !(await this.providerIsCompatibleWithFilters(provider, ['since', 'until'])) |
654 | | - }) |
655 | 723 | this.debouncedFind(this.searchQuery) |
656 | 724 | }, |
657 | 725 | applyQuickDateRange(range) { |
@@ -844,9 +912,27 @@ export default defineComponent({ |
844 | 912 | align-items: center; |
845 | 913 | display: flex; |
846 | 914 | } |
| 915 | +
|
| 916 | + &--unfiltered { |
| 917 | + opacity: 0.7; |
| 918 | + } |
847 | 919 | } |
848 | 920 |
|
849 | 921 | } |
| 922 | +
|
| 923 | + &__unfiltered-header { |
| 924 | + display: flex; |
| 925 | + flex-direction: column; |
| 926 | + gap: 2px; |
| 927 | + margin-block: 16px 8px; |
| 928 | + padding-block: 12px 0; |
| 929 | + border-top: 1px solid var(--color-border); |
| 930 | + } |
| 931 | +
|
| 932 | + &__unfiltered-label { |
| 933 | + font-weight: bold; |
| 934 | + color: var(--color-text-maxcontrast); |
| 935 | + } |
850 | 936 | } |
851 | 937 |
|
852 | 938 | .filter-button__icon { |
|
0 commit comments