diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-07-04 12:14:30 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-07-04 14:47:21 +0200 |
commit | 69275cbda58ca9abbf0d7cb643902e06606b65dc (patch) | |
tree | 5510705cf9b6c27d06c7a089da26592e44dd6f54 | |
parent | b8d3e6420556be354957b2f7cddc5899bb7af272 (diff) | |
download | nextcloud-server-69275cbda58ca9abbf0d7cb643902e06606b65dc.tar.gz nextcloud-server-69275cbda58ca9abbf0d7cb643902e06606b65dc.zip |
feat(files): add "search everywhere" button within the filters row
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | apps/files/src/components/FileListFilter/FileListFilterToSearch.vue | 47 | ||||
-rw-r--r-- | apps/files/src/filters/FilenameFilter.ts | 10 | ||||
-rw-r--r-- | apps/files/src/filters/SearchFilter.ts | 49 | ||||
-rw-r--r-- | apps/files/src/init.ts | 2 | ||||
-rw-r--r-- | apps/files/src/store/search.ts | 1 | ||||
-rw-r--r-- | cypress/e2e/files/search.cy.ts | 47 |
6 files changed, 153 insertions, 3 deletions
diff --git a/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue b/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue new file mode 100644 index 00000000000..938be171f6d --- /dev/null +++ b/apps/files/src/components/FileListFilter/FileListFilterToSearch.vue @@ -0,0 +1,47 @@ +<!-- + - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcButton v-show="isVisible" @click="onClick"> + {{ t('files', 'Search everywhere') }} + </NcButton> +</template> + +<script setup lang="ts"> +import { t } from '@nextcloud/l10n' +import { ref } from 'vue' +import NcButton from '@nextcloud/vue/components/NcButton' +import { getPinia } from '../../store/index.ts' +import { useSearchStore } from '../../store/search.ts' + +const isVisible = ref(false) + +defineExpose({ + hideButton, + showButton, +}) + +/** + * Hide the button - called by the filter class + */ +function hideButton() { + isVisible.value = false +} + +/** + * Show the button - called by the filter class + */ +function showButton() { + isVisible.value = true +} + +/** + * Button click handler to make the filtering a global search. + */ +function onClick() { + const searchStore = useSearchStore(getPinia()) + searchStore.scope = 'globally' +} +</script> diff --git a/apps/files/src/filters/FilenameFilter.ts b/apps/files/src/filters/FilenameFilter.ts index 7914142f6ca..f86269ccd99 100644 --- a/apps/files/src/filters/FilenameFilter.ts +++ b/apps/files/src/filters/FilenameFilter.ts @@ -7,6 +7,8 @@ import type { IFileListFilterChip, INode } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' import { FileListFilter, registerFileListFilter } from '@nextcloud/files' +import { getPinia } from '../store/index.ts' +import { useSearchStore } from '../store/search.ts' /** * Register the filename filter @@ -59,10 +61,14 @@ class FilenameFilter extends FileListFilter { this.updateQuery('') }, }) + } else { + // make sure to also reset the search store when pressing the "X" on the filter chip + const store = useSearchStore(getPinia()) + if (store.scope === 'filter') { + store.query = '' + } } this.updateChips(chips) - // Emit the new query as it might have come not from the Navigation - this.dispatchTypedEvent('update:query', new CustomEvent('update:query', { detail: query })) } } diff --git a/apps/files/src/filters/SearchFilter.ts b/apps/files/src/filters/SearchFilter.ts new file mode 100644 index 00000000000..4c7231fd26a --- /dev/null +++ b/apps/files/src/filters/SearchFilter.ts @@ -0,0 +1,49 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { INode } from '@nextcloud/files' +import type { ComponentPublicInstance } from 'vue' + +import { subscribe } from '@nextcloud/event-bus' +import { FileListFilter, registerFileListFilter } from '@nextcloud/files' +import Vue from 'vue' +import FileListFilterToSearch from '../components/FileListFilter/FileListFilterToSearch.vue' + +class SearchFilter extends FileListFilter { + + private currentInstance?: ComponentPublicInstance<typeof FileListFilterToSearch> + + constructor() { + super('files:filter-to-search', 999) + subscribe('files:search:updated', ({ query, scope }) => { + if (query && scope === 'filter') { + this.currentInstance?.showButton() + } else { + this.currentInstance?.hideButton() + } + }) + } + + public mount(el: HTMLElement) { + if (this.currentInstance) { + this.currentInstance.$destroy() + } + + const View = Vue.extend(FileListFilterToSearch) + this.currentInstance = new View().$mount(el) as unknown as ComponentPublicInstance<typeof FileListFilterToSearch> + } + + public filter(nodes: INode[]): INode[] { + return nodes + } + +} + +/** + * Register a file list filter to only show hidden files if enabled by user config + */ +export function registerFilterToSearchToggle() { + registerFileListFilter(new SearchFilter()) +} diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index a9aedb5fb63..710284f691c 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -36,6 +36,7 @@ import { initLivePhotos } from './services/LivePhotos' import { isPublicShare } from '@nextcloud/sharing/public' import { registerConvertActions } from './actions/convertAction.ts' import { registerFilenameFilter } from './filters/FilenameFilter.ts' +import { registerFilterToSearchToggle } from './filters/SearchFilter.ts' // Register file actions registerConvertActions() @@ -70,6 +71,7 @@ registerHiddenFilesFilter() registerTypeFilter() registerModifiedFilter() registerFilenameFilter() +registerFilterToSearchToggle() // Register preview service worker registerPreviewServiceWorker() diff --git a/apps/files/src/store/search.ts b/apps/files/src/store/search.ts index 286cad253fc..417f662ca00 100644 --- a/apps/files/src/store/search.ts +++ b/apps/files/src/store/search.ts @@ -83,7 +83,6 @@ export const useSearchStore = defineStore('search', () => { function updateSearch() { // emit the search event to update the filter emit('files:search:updated', { query: query.value, scope: scope.value }) - const router = window.OCP.Files.Router as RouterService // if we are on the search view and the query was unset or scope was set to 'filter' we need to move back to the files view diff --git a/cypress/e2e/files/search.cy.ts b/cypress/e2e/files/search.cy.ts index eeb08da5a22..be3a7059382 100644 --- a/cypress/e2e/files/search.cy.ts +++ b/cypress/e2e/files/search.cy.ts @@ -75,6 +75,53 @@ describe('files: search', () => { cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2) }) + it('See "search everywhere" button', () => { + // Not visible initially + cy.get('[data-cy-files-filters]') + .findByRole('button', { name: /Search everywhere/i }) + .should('not.to.exist') + + // add a filter + navigation.searchInput().type('file') + + // see its visible + cy.get('[data-cy-files-filters]') + .findByRole('button', { name: /Search everywhere/i }) + .should('be.visible') + + // clear the filter + navigation.searchClearButton().click() + + // see its not visible again + cy.get('[data-cy-files-filters]') + .findByRole('button', { name: /Search everywhere/i }) + .should('not.to.exist') + }) + + it('can make local search a global search', () => { + navigateToFolder('some folder') + getRowForFile('a file.txt').should('be.visible') + + navigation.searchInput().type('file') + + // see local results + getRowForFile('a file.txt').should('be.visible') + getRowForFile('a second file.txt').should('be.visible') + cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2) + + // toggle global search + cy.get('[data-cy-files-filters]') + .findByRole('button', { name: /Search everywhere/i }) + .should('be.visible') + .click() + + // see global results + getRowForFile('file.txt').should('be.visible') + getRowForFile('a file.txt').should('be.visible') + getRowForFile('a second file.txt').should('be.visible') + getRowForFile('another file.txt').should('be.visible') + }) + it('shows empty content when there are no results', () => { navigateToFolder('some folder') getRowForFile('a file.txt').should('be.visible') |