aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2025-07-04 12:14:30 +0200
committerFerdinand Thiessen <opensource@fthiessen.de>2025-07-04 14:47:21 +0200
commit69275cbda58ca9abbf0d7cb643902e06606b65dc (patch)
tree5510705cf9b6c27d06c7a089da26592e44dd6f54
parentb8d3e6420556be354957b2f7cddc5899bb7af272 (diff)
downloadnextcloud-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.vue47
-rw-r--r--apps/files/src/filters/FilenameFilter.ts10
-rw-r--r--apps/files/src/filters/SearchFilter.ts49
-rw-r--r--apps/files/src/init.ts2
-rw-r--r--apps/files/src/store/search.ts1
-rw-r--r--cypress/e2e/files/search.cy.ts47
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')