Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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>
10 changes: 8 additions & 2 deletions apps/files/src/filters/FilenameFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }))
}
}

Expand Down
49 changes: 49 additions & 0 deletions apps/files/src/filters/SearchFilter.ts
Original file line number Diff line number Diff line change
@@ -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())
}
2 changes: 2 additions & 0 deletions apps/files/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -70,6 +71,7 @@ registerHiddenFilesFilter()
registerTypeFilter()
registerModifiedFilter()
registerFilenameFilter()
registerFilterToSearchToggle()

// Register preview service worker
registerPreviewServiceWorker()
Expand Down
1 change: 0 additions & 1 deletion apps/files/src/store/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions cypress/e2e/files/search.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 2 additions & 2 deletions dist/files-init.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/files-init.js.map

Large diffs are not rendered by default.

Loading