mirror of
https://github.com/nextcloud/server.git
synced 2026-06-10 09:13:19 -04:00
Merge pull request #53826 from nextcloud/feat/search-while-filtering
This commit is contained in:
commit
77939fad04
54 changed files with 281 additions and 234 deletions
|
|
@ -281,7 +281,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// Make sure we set the node as active
|
||||
this.activeStore.setActiveNode(this.source)
|
||||
this.activeStore.activeNode = this.source
|
||||
|
||||
// Execute the action
|
||||
await executeAction(action)
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ export default defineComponent({
|
|||
delete query.openfile
|
||||
delete query.opendetails
|
||||
|
||||
this.activeStore.clearActiveNode()
|
||||
this.activeStore.activeNode = undefined
|
||||
window.OCP.Files.Router.goToRoute(
|
||||
null,
|
||||
{ ...this.$route.params, fileid: String(this.currentFolder.fileid ?? '') },
|
||||
|
|
@ -449,7 +449,7 @@ export default defineComponent({
|
|||
delete query.openfile
|
||||
delete query.opendetails
|
||||
|
||||
this.activeStore.setActiveNode(node)
|
||||
this.activeStore.activeNode = node
|
||||
|
||||
// Silent update of the URL
|
||||
window.OCP.Files.Router.goToRoute(
|
||||
|
|
|
|||
|
|
@ -13,15 +13,10 @@ import NcAppNavigationSearch from '@nextcloud/vue/components/NcAppNavigationSear
|
|||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import { onBeforeNavigation } from '../composables/useBeforeNavigation.ts'
|
||||
import { useNavigation } from '../composables/useNavigation.ts'
|
||||
import { useRouteParameters } from '../composables/useRouteParameters.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
import { useSearchStore } from '../store/search.ts'
|
||||
import { VIEW_ID } from '../views/search.ts'
|
||||
|
||||
const { currentView } = useNavigation(true)
|
||||
const { directory } = useRouteParameters()
|
||||
|
||||
const filesStore = useFilesStore()
|
||||
const searchStore = useSearchStore()
|
||||
|
||||
/**
|
||||
|
|
@ -55,44 +50,19 @@ onBeforeNavigation((to, from, next) => {
|
|||
*/
|
||||
const isSearchView = computed(() => currentView.value.id === VIEW_ID)
|
||||
|
||||
/**
|
||||
* Local search is only possible on real DAV resources within the files root
|
||||
*/
|
||||
const canSearchLocally = computed(() => {
|
||||
if (searchStore.base) {
|
||||
return true
|
||||
}
|
||||
|
||||
const folder = filesStore.getDirectoryByPath(currentView.value.id, directory.value)
|
||||
return folder?.isDavResource && folder?.root?.startsWith('/files/')
|
||||
})
|
||||
|
||||
/**
|
||||
* Different searchbox label depending if filtering or searching
|
||||
*/
|
||||
const searchLabel = computed(() => {
|
||||
if (searchStore.scope === 'globally') {
|
||||
return t('files', 'Search globally by filename …')
|
||||
} else if (searchStore.scope === 'locally') {
|
||||
return t('files', 'Search here by filename …')
|
||||
}
|
||||
return t('files', 'Filter file names …')
|
||||
return t('files', 'Search here by filename …')
|
||||
})
|
||||
|
||||
/**
|
||||
* Update the search value and set the base if needed
|
||||
* @param value - The new value
|
||||
*/
|
||||
function onUpdateSearch(value: string) {
|
||||
if (searchStore.scope === 'locally' && currentView.value.id !== VIEW_ID) {
|
||||
searchStore.base = filesStore.getDirectoryByPath(currentView.value.id, directory.value)
|
||||
}
|
||||
searchStore.query = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcAppNavigationSearch :label="searchLabel" :model-value="searchStore.query" @update:modelValue="onUpdateSearch">
|
||||
<NcAppNavigationSearch v-model="searchStore.query" :label="searchLabel">
|
||||
<template #actions>
|
||||
<NcActions :aria-label="t('files', 'Search scope options')" :disabled="isSearchView">
|
||||
<template #icon>
|
||||
|
|
@ -102,13 +72,7 @@ function onUpdateSearch(value: string) {
|
|||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiMagnify" />
|
||||
</template>
|
||||
{{ t('files', 'Filter in current view') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="canSearchLocally" close-after-click @click="searchStore.scope = 'locally'">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiMagnify" />
|
||||
</template>
|
||||
{{ t('files', 'Search from this location') }}
|
||||
{{ t('files', 'Filter and search from this location') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton close-after-click @click="searchStore.scope = 'globally'">
|
||||
<template #icon>
|
||||
|
|
|
|||
|
|
@ -2,25 +2,55 @@
|
|||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { ContentsWithRoot, File, Folder } from '@nextcloud/files'
|
||||
import type { ContentsWithRoot, File, Folder, Node } from '@nextcloud/files'
|
||||
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
||||
|
||||
import { davGetDefaultPropfind, davResultToNode, davRootPath } from '@nextcloud/files'
|
||||
import { defaultRootPath, getDefaultPropfind, resultToNode as davResultToNode } from '@nextcloud/files/dav'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { join } from 'path'
|
||||
import { client } from './WebdavClient.ts'
|
||||
import { searchNodes } from './WebDavSearch.ts'
|
||||
import { getPinia } from '../store/index.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
import { useSearchStore } from '../store/search.ts'
|
||||
import logger from '../logger.ts'
|
||||
|
||||
/**
|
||||
* Slim wrapper over `@nextcloud/files` `davResultToNode` to allow using the function with `Array.map`
|
||||
* @param stat The result returned by the webdav library
|
||||
*/
|
||||
export const resultToNode = (stat: FileStat): File | Folder => davResultToNode(stat)
|
||||
export const resultToNode = (stat: FileStat): Node => davResultToNode(stat)
|
||||
|
||||
export const getContents = (path = '/'): CancelablePromise<ContentsWithRoot> => {
|
||||
path = join(davRootPath, path)
|
||||
/**
|
||||
* Get contents implementation for the files view.
|
||||
* This also allows to fetch local search results when the user is currently filtering.
|
||||
*
|
||||
* @param path - The path to query
|
||||
*/
|
||||
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
|
||||
const controller = new AbortController()
|
||||
const propfindPayload = davGetDefaultPropfind()
|
||||
const searchStore = useSearchStore(getPinia())
|
||||
|
||||
if (searchStore.query.length >= 3) {
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
cancel(() => controller.abort())
|
||||
getLocalSearch(path, searchStore.query, controller.signal)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
} else {
|
||||
return defaultGetContents(path)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic `getContents` implementation for the users files.
|
||||
*
|
||||
* @param path - The path to get the contents
|
||||
*/
|
||||
export function defaultGetContents(path: string): CancelablePromise<ContentsWithRoot> {
|
||||
path = join(defaultRootPath, path)
|
||||
const controller = new AbortController()
|
||||
const propfindPayload = getDefaultPropfind()
|
||||
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
onCancel(() => controller.abort())
|
||||
|
|
@ -56,3 +86,25 @@ export const getContents = (path = '/'): CancelablePromise<ContentsWithRoot> =>
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local search results for the current folder.
|
||||
*
|
||||
* @param path - The path
|
||||
* @param query - The current search query
|
||||
* @param signal - The aboort signal
|
||||
*/
|
||||
async function getLocalSearch(path: string, query: string, signal: AbortSignal): Promise<ContentsWithRoot> {
|
||||
const filesStore = useFilesStore(getPinia())
|
||||
let folder = filesStore.getDirectoryByPath('files', path)
|
||||
if (!folder) {
|
||||
const rootPath = join(defaultRootPath, path)
|
||||
const stat = await client.stat(rootPath, { details: true }) as ResponseDataDetailed<FileStat>
|
||||
folder = resultToNode(stat.data) as Folder
|
||||
}
|
||||
const contents = await searchNodes(query, { dir: path, signal })
|
||||
return {
|
||||
folder,
|
||||
contents,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ describe('HotKeysService testing', () => {
|
|||
})
|
||||
|
||||
// Setting the view first as it reset the active node
|
||||
activeStore.onChangedView(view)
|
||||
activeStore.setActiveNode(file)
|
||||
activeStore.activeView = view
|
||||
activeStore.activeNode = file
|
||||
|
||||
window.OCA = { Files: { Sidebar: { open: () => {}, setActiveTab: () => {} } } }
|
||||
// We only mock what needed, we do not need Files.Router.goTo or Files.Navigation
|
||||
|
|
|
|||
|
|
@ -21,12 +21,11 @@ export function getContents(): CancelablePromise<ContentsWithRoot> {
|
|||
const controller = new AbortController()
|
||||
|
||||
const searchStore = useSearchStore(getPinia())
|
||||
const dir = searchStore.base?.path
|
||||
|
||||
return new CancelablePromise<ContentsWithRoot>(async (resolve, reject, cancel) => {
|
||||
cancel(() => controller.abort())
|
||||
try {
|
||||
const contents = await searchNodes(searchStore.query, { dir, signal: controller.signal })
|
||||
const contents = await searchNodes(searchStore.query, { signal: controller.signal })
|
||||
resolve({
|
||||
contents,
|
||||
folder: new Folder({
|
||||
|
|
|
|||
|
|
@ -3,74 +3,84 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { ActiveStore } from '../types.ts'
|
||||
import type { FileAction, Node, View } from '@nextcloud/files'
|
||||
import type { FileAction, View, Node, Folder } from '@nextcloud/files'
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { getNavigation } from '@nextcloud/files'
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
import { getNavigation } from '@nextcloud/files'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import logger from '../logger.ts'
|
||||
|
||||
export const useActiveStore = function(...args) {
|
||||
const store = defineStore('active', {
|
||||
state: () => ({
|
||||
_initialized: false,
|
||||
activeNode: null,
|
||||
activeView: null,
|
||||
activeAction: null,
|
||||
} as ActiveStore),
|
||||
export const useActiveStore = defineStore('active', () => {
|
||||
/**
|
||||
* The currently active action
|
||||
*/
|
||||
const activeAction = ref<FileAction>()
|
||||
|
||||
actions: {
|
||||
setActiveNode(node: Node) {
|
||||
if (!node) {
|
||||
throw new Error('Use clearActiveNode to clear the active node')
|
||||
}
|
||||
logger.debug('Setting active node', { node })
|
||||
this.activeNode = node
|
||||
},
|
||||
/**
|
||||
* The currently active folder
|
||||
*/
|
||||
const activeFolder = ref<Folder>()
|
||||
|
||||
clearActiveNode() {
|
||||
this.activeNode = null
|
||||
},
|
||||
/**
|
||||
* The current active node within the folder
|
||||
*/
|
||||
const activeNode = ref<Node>()
|
||||
|
||||
onDeletedNode(node: Node) {
|
||||
if (this.activeNode && this.activeNode.source === node.source) {
|
||||
this.clearActiveNode()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The current active view
|
||||
*/
|
||||
const activeView = ref<View>()
|
||||
|
||||
setActiveAction(action: FileAction) {
|
||||
this.activeAction = action
|
||||
},
|
||||
initialize()
|
||||
|
||||
clearActiveAction() {
|
||||
this.activeAction = null
|
||||
},
|
||||
/**
|
||||
* Unset the active node if deleted
|
||||
*
|
||||
* @param node - The node thats deleted
|
||||
* @private
|
||||
*/
|
||||
function onDeletedNode(node: Node) {
|
||||
if (activeNode.value && activeNode.value.source === node.source) {
|
||||
activeNode.value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
onChangedView(view: View|null = null) {
|
||||
logger.debug('Setting active view', { view })
|
||||
this.activeView = view
|
||||
this.clearActiveNode()
|
||||
},
|
||||
},
|
||||
})
|
||||
/**
|
||||
* Callback to update the current active view
|
||||
*
|
||||
* @param view - The new active view
|
||||
* @private
|
||||
*/
|
||||
function onChangedView(view: View|null = null) {
|
||||
logger.debug('Setting active view', { view })
|
||||
activeView.value = view ?? undefined
|
||||
activeNode.value = undefined
|
||||
}
|
||||
|
||||
const activeStore = store(...args)
|
||||
const navigation = getNavigation()
|
||||
/**
|
||||
* Initalize the store - connect all event listeners.
|
||||
* @private
|
||||
*/
|
||||
function initialize() {
|
||||
const navigation = getNavigation()
|
||||
|
||||
// Make sure we only register the listeners once
|
||||
if (!activeStore._initialized) {
|
||||
subscribe('files:node:deleted', activeStore.onDeletedNode)
|
||||
// Make sure we only register the listeners once
|
||||
subscribe('files:node:deleted', onDeletedNode)
|
||||
|
||||
activeStore._initialized = true
|
||||
activeStore.onChangedView(navigation.active)
|
||||
onChangedView(navigation.active)
|
||||
|
||||
// Or you can react to changes of the current active view
|
||||
navigation.addEventListener('updateActive', (event) => {
|
||||
activeStore.onChangedView(event.detail)
|
||||
onChangedView(event.detail)
|
||||
})
|
||||
}
|
||||
|
||||
return activeStore
|
||||
}
|
||||
return {
|
||||
activeAction,
|
||||
activeFolder,
|
||||
activeNode,
|
||||
activeView,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export const useFilesStore = function(...args) {
|
|||
}
|
||||
|
||||
// If we have only one node with the file ID, we can update it directly
|
||||
if (node.source === nodes[0].source) {
|
||||
if (nodes.length === 1 && node.source === nodes[0].source) {
|
||||
this.updateNodes([node])
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { INode, View } from '@nextcloud/files'
|
||||
import type RouterService from '../services/RouterService'
|
||||
import type { SearchScope } from '../types'
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type RouterService from '../services/RouterService.ts'
|
||||
import type { SearchScope } from '../types.ts'
|
||||
|
||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||
import debounce from 'debounce'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
import { VIEW_ID } from '../views/search'
|
||||
import logger from '../logger'
|
||||
import debounce from 'debounce'
|
||||
import { VIEW_ID } from '../views/search.ts'
|
||||
import logger from '../logger.ts'
|
||||
|
||||
export const useSearchStore = defineStore('search', () => {
|
||||
/**
|
||||
|
|
@ -20,28 +20,16 @@ export const useSearchStore = defineStore('search', () => {
|
|||
*/
|
||||
const query = ref('')
|
||||
|
||||
/**
|
||||
* Where to start the search
|
||||
*/
|
||||
const base = ref<INode>()
|
||||
|
||||
/**
|
||||
* Scope of the search.
|
||||
* Scopes:
|
||||
* - filter: only filter current file list
|
||||
* - locally: search from current location recursivly
|
||||
* - globally: search everywhere
|
||||
*/
|
||||
const scope = ref<SearchScope>('filter')
|
||||
|
||||
// reset the base if query is cleared
|
||||
watch(scope, () => {
|
||||
if (scope.value !== 'locally') {
|
||||
base.value = undefined
|
||||
}
|
||||
|
||||
updateSearch()
|
||||
})
|
||||
watch(scope, updateSearch)
|
||||
|
||||
watch(query, (old, current) => {
|
||||
// skip if only whitespaces changed
|
||||
|
|
@ -59,13 +47,12 @@ export const useSearchStore = defineStore('search', () => {
|
|||
* Debounced update of the current route
|
||||
* @private
|
||||
*/
|
||||
const updateRouter = debounce((isSearch: boolean, fileid?: number) => {
|
||||
const updateRouter = debounce((isSearch: boolean) => {
|
||||
const router = window.OCP.Files.Router as RouterService
|
||||
router.goToRoute(
|
||||
undefined,
|
||||
{
|
||||
view: VIEW_ID,
|
||||
...(fileid === undefined ? {} : { fileid: String(fileid) }),
|
||||
},
|
||||
{
|
||||
query: query.value,
|
||||
|
|
@ -106,12 +93,10 @@ export const useSearchStore = defineStore('search', () => {
|
|||
return
|
||||
}
|
||||
|
||||
// we only use the directory if we search locally
|
||||
const fileid = scope.value === 'locally' ? base.value?.fileid : undefined
|
||||
const isSearch = router.params.view === VIEW_ID
|
||||
|
||||
logger.debug('Update route for updated search query', { query: query.value, fileid, isSearch })
|
||||
updateRouter(isSearch, fileid)
|
||||
logger.debug('Update route for updated search query', { query: query.value, isSearch })
|
||||
updateRouter(isSearch)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -162,7 +147,6 @@ export const useSearchStore = defineStore('search', () => {
|
|||
}
|
||||
|
||||
return {
|
||||
base,
|
||||
query,
|
||||
scope,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,16 +107,16 @@ export interface DragAndDropStore {
|
|||
|
||||
// Active node store
|
||||
export interface ActiveStore {
|
||||
_initialized: boolean
|
||||
activeAction: FileAction|null
|
||||
activeFolder: Folder|null
|
||||
activeNode: Node|null
|
||||
activeView: View|null
|
||||
activeAction: FileAction|null
|
||||
}
|
||||
|
||||
/**
|
||||
* Search scope for the in-files-search
|
||||
*/
|
||||
export type SearchScope = 'filter'|'locally'|'globally'
|
||||
export type SearchScope = 'filter'|'globally'
|
||||
|
||||
export interface TemplateFile {
|
||||
app: string
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const executeAction = async (action: FileAction) => {
|
|||
try {
|
||||
// Set the loading marker
|
||||
Vue.set(currentNode, 'status', NodeStatus.LOADING)
|
||||
activeStore.setActiveAction(action)
|
||||
activeStore.activeAction = action
|
||||
|
||||
const success = await action.exec(currentNode, currentView, currentDir)
|
||||
|
||||
|
|
@ -69,6 +69,6 @@ export const executeAction = async (action: FileAction) => {
|
|||
} finally {
|
||||
// Reset the loading marker
|
||||
Vue.set(currentNode, 'status', undefined)
|
||||
activeStore.clearActiveAction()
|
||||
activeStore.activeAction = undefined
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ import { getCapabilities } from '@nextcloud/capabilities'
|
|||
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
import { Node, Permission, sortNodes, getFileListActions } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { join, dirname, normalize } from 'path'
|
||||
import { join, dirname, normalize, relative } from 'path'
|
||||
import { showError, showSuccess, showWarning } from '@nextcloud/dialogs'
|
||||
import { ShareType } from '@nextcloud/sharing'
|
||||
import { UploadPicker, UploadStatus } from '@nextcloud/upload'
|
||||
|
|
@ -188,6 +188,7 @@ import { useFiltersStore } from '../store/filters.ts'
|
|||
import { useNavigation } from '../composables/useNavigation.ts'
|
||||
import { usePathsStore } from '../store/paths.ts'
|
||||
import { useRouteParameters } from '../composables/useRouteParameters.ts'
|
||||
import { useActiveStore } from '../store/active.ts'
|
||||
import { useSelectionStore } from '../store/selection.ts'
|
||||
import { useUploaderStore } from '../store/uploader.ts'
|
||||
import { useUserConfigStore } from '../store/userconfig.ts'
|
||||
|
|
@ -240,6 +241,8 @@ export default defineComponent({
|
|||
const { currentView } = useNavigation()
|
||||
const { directory, fileId } = useRouteParameters()
|
||||
const fileListWidth = useFileListWidth()
|
||||
|
||||
const activeStore = useActiveStore()
|
||||
const filesStore = useFilesStore()
|
||||
const filtersStore = useFiltersStore()
|
||||
const pathsStore = usePathsStore()
|
||||
|
|
@ -259,6 +262,7 @@ export default defineComponent({
|
|||
headers: useFileListHeaders(),
|
||||
t,
|
||||
|
||||
activeStore,
|
||||
filesStore,
|
||||
filtersStore,
|
||||
pathsStore,
|
||||
|
|
@ -322,7 +326,7 @@ export default defineComponent({
|
|||
* The current folder.
|
||||
*/
|
||||
currentFolder(): Folder | undefined {
|
||||
if (!this.currentView?.id) {
|
||||
if (!this.currentView) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -352,12 +356,28 @@ export default defineComponent({
|
|||
return this.isAscSorting ? results : results.reverse()
|
||||
}
|
||||
|
||||
return sortNodes(this.dirContentsFiltered, {
|
||||
const nodes = sortNodes(this.dirContentsFiltered, {
|
||||
sortFavoritesFirst: this.userConfig.sort_favorites_first,
|
||||
sortFoldersFirst: this.userConfig.sort_folders_first,
|
||||
sortingMode: this.sortingMode,
|
||||
sortingOrder: this.isAscSorting ? 'asc' : 'desc',
|
||||
})
|
||||
|
||||
// TODO upstream this
|
||||
if (this.currentView.id === 'files') {
|
||||
nodes.sort((a, b) => {
|
||||
const aa = relative(a.source, this.currentFolder!.source) === '..'
|
||||
const bb = relative(b.source, this.currentFolder!.source) === '..'
|
||||
if (aa && bb) {
|
||||
return 0
|
||||
} else if (aa) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
}
|
||||
|
||||
return nodes
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -492,6 +512,10 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
currentFolder() {
|
||||
this.activeStore.activeFolder = this.currentFolder
|
||||
},
|
||||
|
||||
currentView(newView, oldView) {
|
||||
if (newView?.id === oldView?.id) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
import { mdiMagnifyClose } from '@mdi/js'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import debounce from 'debounce'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcInputField from '@nextcloud/vue/components/NcInputField'
|
||||
|
|
@ -32,9 +31,6 @@ const debouncedUpdate = debounce((value: string) => {
|
|||
:model-value="searchStore.query"
|
||||
type="search"
|
||||
@update:model-value="debouncedUpdate" />
|
||||
<NcButton v-if="searchStore.scope === 'locally'" @click="searchStore.scope = 'globally'">
|
||||
{{ t('files', 'Search globally') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||
import { View, getNavigation } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { getContents } from '../services/Files.ts'
|
||||
import { useActiveStore } from '../store/active.ts'
|
||||
import { defaultView } from '../utils/filesViews.ts'
|
||||
|
||||
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
|
||||
|
|
@ -16,6 +18,9 @@ export const VIEW_ID = 'files'
|
|||
* Register the files view to the navigation
|
||||
*/
|
||||
export function registerFilesView() {
|
||||
// we cache the query to allow more performant search (see below in event listener)
|
||||
let oldQuery = ''
|
||||
|
||||
const Navigation = getNavigation()
|
||||
Navigation.register(new View({
|
||||
id: VIEW_ID,
|
||||
|
|
@ -28,4 +33,33 @@ export function registerFilesView() {
|
|||
|
||||
getContents,
|
||||
}))
|
||||
|
||||
// when the search is updated
|
||||
// and we are in the files view
|
||||
// and there is already a folder fetched
|
||||
// then we "update" it to trigger a new `getContents` call to search for the query while the filelist is filtered
|
||||
subscribe('files:search:updated', ({ scope, query }) => {
|
||||
if (scope === 'globally') {
|
||||
return
|
||||
}
|
||||
|
||||
if (Navigation.active?.id !== VIEW_ID) {
|
||||
return
|
||||
}
|
||||
|
||||
// If neither the old query nor the new query is longer than the search minimum
|
||||
// then we do not need to trigger a new PROPFIND / SEARCH
|
||||
// so we skip unneccessary requests here
|
||||
if (oldQuery.length < 3 && query.length < 3) {
|
||||
return
|
||||
}
|
||||
|
||||
const store = useActiveStore()
|
||||
if (!store.activeFolder) {
|
||||
return
|
||||
}
|
||||
|
||||
oldQuery = query
|
||||
emit('files:node:updated', store.activeFolder)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ describe('HotKeysService testing', () => {
|
|||
})
|
||||
|
||||
// Setting the view first as it reset the active node
|
||||
activeStore.onChangedView(view)
|
||||
activeStore.setActiveNode(file)
|
||||
activeStore.activeView = view
|
||||
activeStore.activeNode = file
|
||||
})
|
||||
|
||||
it('Pressing t should open the tag management dialog', () => {
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ describe('files: search', () => {
|
|||
cy.createRandomUser().then(($user) => {
|
||||
user = $user
|
||||
cy.mkdir(user, '/some folder')
|
||||
cy.mkdir(user, '/some folder/nested folder')
|
||||
cy.mkdir(user, '/other folder')
|
||||
cy.mkdir(user, '/12345')
|
||||
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/file.txt')
|
||||
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/a file.txt')
|
||||
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/a second file.txt')
|
||||
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/nested folder/deep file.txt')
|
||||
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/other folder/another file.txt')
|
||||
cy.login(user)
|
||||
})
|
||||
|
|
@ -58,21 +60,16 @@ describe('files: search', () => {
|
|||
getRowForFile('another file.txt').should('be.visible')
|
||||
})
|
||||
|
||||
it('can search locally', () => {
|
||||
it('filter does also search locally', () => {
|
||||
navigateToFolder('some folder')
|
||||
getRowForFile('a file.txt').should('be.visible')
|
||||
|
||||
navigation.searchScopeTrigger().click()
|
||||
navigation.searchScopeMenu()
|
||||
.should('be.visible')
|
||||
.findByRole('menuitem', { name: /search from this location/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
navigation.searchInput().type('file')
|
||||
|
||||
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)
|
||||
getRowForFile('deep file.txt').should('be.visible')
|
||||
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('See "search everywhere" button', () => {
|
||||
|
|
@ -107,7 +104,8 @@ describe('files: search', () => {
|
|||
// 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)
|
||||
getRowForFile('deep file.txt').should('be.visible')
|
||||
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 3)
|
||||
|
||||
// toggle global search
|
||||
cy.get('[data-cy-files-filters]')
|
||||
|
|
@ -118,6 +116,7 @@ describe('files: search', () => {
|
|||
// see global results
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
getRowForFile('a file.txt').should('be.visible')
|
||||
getRowForFile('deep file.txt').should('be.visible')
|
||||
getRowForFile('a second file.txt').should('be.visible')
|
||||
getRowForFile('another file.txt').should('be.visible')
|
||||
})
|
||||
|
|
@ -129,49 +128,22 @@ describe('files: search', () => {
|
|||
navigation.searchScopeTrigger().click()
|
||||
navigation.searchScopeMenu()
|
||||
.should('be.visible')
|
||||
.findByRole('menuitem', { name: /search from this location/i })
|
||||
.findByRole('menuitem', { name: /search globally/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
navigation.searchInput().type('folder')
|
||||
navigation.searchInput().type('xyz')
|
||||
|
||||
// see the empty content message
|
||||
cy.contains('[role="note"]', /No search results for .folder./)
|
||||
cy.contains('[role="note"]', /No search results for .xyz./)
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
// see within there is a search box with the same value
|
||||
cy.findByRole('searchbox', { name: /search for files/i })
|
||||
.should('be.visible')
|
||||
.and('have.value', 'folder')
|
||||
// and we can switch from local to global search
|
||||
cy.findByRole('button', { name: 'Search globally' })
|
||||
.should('be.visible')
|
||||
.and('have.value', 'xyz')
|
||||
})
|
||||
})
|
||||
|
||||
it('can turn local search into global search', () => {
|
||||
navigateToFolder('some folder')
|
||||
getRowForFile('a file.txt').should('be.visible')
|
||||
|
||||
navigation.searchScopeTrigger().click()
|
||||
navigation.searchScopeMenu()
|
||||
.should('be.visible')
|
||||
.findByRole('menuitem', { name: /search from this location/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
navigation.searchInput().type('folder')
|
||||
|
||||
// see the empty content message and turn into global search
|
||||
cy.contains('[role="note"]', /No search results for .folder./)
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Search globally' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
getRowForFile('some folder').should('be.visible')
|
||||
getRowForFile('other folder').should('be.visible')
|
||||
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
|
||||
})
|
||||
|
||||
it('can alter search', () => {
|
||||
navigation.searchScopeTrigger().click()
|
||||
navigation.searchScopeMenu()
|
||||
|
|
|
|||
2
dist/175-175.js
vendored
2
dist/175-175.js
vendored
|
|
@ -1,2 +0,0 @@
|
|||
"use strict";(self.webpackChunknextcloud=self.webpackChunknextcloud||[]).push([[175],{50540:(e,t,n)=>{n.d(t,{A:()=>l});var s=n(71354),a=n.n(s),r=n(76314),c=n.n(r)()(a());c.push([e.id,".search-empty-view__input[data-v-61d86e6e]{flex:0 1;min-width:min(400px,50vw)}.search-empty-view__wrapper[data-v-61d86e6e]{display:flex;flex-wrap:wrap;gap:10px;align-items:baseline}","",{version:3,sources:["webpack://./apps/files/src/views/SearchEmptyView.vue"],names:[],mappings:"AAEC,2CACC,QAAA,CACA,yBAAA,CAGD,6CACC,YAAA,CACA,cAAA,CACA,QAAA,CACA,oBAAA",sourcesContent:["\n.search-empty-view {\n\t&__input {\n\t\tflex: 0 1;\n\t\tmin-width: min(400px, 50vw);\n\t}\n\n\t&__wrapper {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tgap: 10px;\n\t\talign-items: baseline;\n\t}\n}\n"],sourceRoot:""}]);const l=c},60175:(e,t,n)=>{n.r(t),n.d(t,{default:()=>q});var s=n(85471),a=n(9165),r=n(53334),c=n(17334),l=n.n(c),p=n(97012),o=n(32190),i=n(6695),u=n(16879),A=n(4114),d=n(82736);const y=(0,s.pM)({__name:"SearchEmptyView",setup(e){const t=(0,d.j)((0,A.u)()),n=l()((e=>{t.query=e}),500);return{__sfc:!0,searchStore:t,debouncedUpdate:n,mdiMagnifyClose:a.WBH,t:r.t,NcButton:p.A,NcEmptyContent:o.A,NcIconSvgWrapper:i.A,NcInputField:u.A}}});var f=n(85072),m=n.n(f),h=n(97825),C=n.n(h),_=n(77659),v=n.n(_),w=n(55056),x=n.n(w),S=n(10540),b=n.n(S),g=n(41113),N=n.n(g),k=n(50540),E={};E.styleTagTransform=N(),E.setAttributes=x(),E.insert=v().bind(null,"head"),E.domAPI=C(),E.insertStyleElement=b(),m()(k.A,E),k.A&&k.A.locals&&k.A.locals;const q=(0,n(14486).A)(y,(function(){var e=this,t=e._self._c,n=e._self._setupProxy;return t(n.NcEmptyContent,{attrs:{name:n.t("files","No search results for “{query}”",{query:n.searchStore.query})},scopedSlots:e._u([{key:"icon",fn:function(){return[t(n.NcIconSvgWrapper,{attrs:{path:n.mdiMagnifyClose}})]},proxy:!0},{key:"action",fn:function(){return[t("div",{staticClass:"search-empty-view__wrapper"},[t(n.NcInputField,{staticClass:"search-empty-view__input",attrs:{label:n.t("files","Search for files"),"model-value":n.searchStore.query,type:"search"},on:{"update:model-value":n.debouncedUpdate}}),e._v(" "),"locally"===n.searchStore.scope?t(n.NcButton,{on:{click:function(e){n.searchStore.scope="globally"}}},[e._v("\n\t\t\t\t"+e._s(n.t("files","Search globally"))+"\n\t\t\t")]):e._e()],1)]},proxy:!0}])})}),[],!1,null,"61d86e6e",null).exports}}]);
|
||||
//# sourceMappingURL=175-175.js.map?v=a61155e5a3ee1e107813
|
||||
1
dist/175-175.js.map
vendored
1
dist/175-175.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/175-175.js.map.license
vendored
1
dist/175-175.js.map.license
vendored
|
|
@ -1 +0,0 @@
|
|||
175-175.js.license
|
||||
2
dist/7457-7457.js
vendored
Normal file
2
dist/7457-7457.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/7457-7457.js.map
vendored
Normal file
1
dist/7457-7457.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/7457-7457.js.map.license
vendored
Symbolic link
1
dist/7457-7457.js.map.license
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
7457-7457.js.license
|
||||
4
dist/comments-comments-app.js
vendored
4
dist/comments-comments-app.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-comments-app.js.map
vendored
2
dist/comments-comments-app.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/comments-comments-tab.js
vendored
4
dist/comments-comments-tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-comments-tab.js.map
vendored
2
dist/comments-comments-tab.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-init.js
vendored
4
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-init-public.js
vendored
4
dist/files_sharing-init-public.js
vendored
File diff suppressed because one or more lines are too long
12
dist/files_sharing-init-public.js.license
vendored
12
dist/files_sharing-init-public.js.license
vendored
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
|
|||
SPDX-FileCopyrightText: string_decoder developers
|
||||
SPDX-FileCopyrightText: inherits developers
|
||||
SPDX-FileCopyrightText: escape-html developers
|
||||
SPDX-FileCopyrightText: debounce developers
|
||||
SPDX-FileCopyrightText: Tobias Koppers @sokra
|
||||
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
|
||||
SPDX-FileCopyrightText: Sindre Sorhus
|
||||
|
|
@ -16,9 +17,11 @@ SPDX-FileCopyrightText: Roeland Jago Douma
|
|||
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-FileCopyrightText: Joyent
|
||||
SPDX-FileCopyrightText: Jonas Schade <derzade@gmail.com>
|
||||
SPDX-FileCopyrightText: Guillaume Chau
|
||||
SPDX-FileCopyrightText: GitHub Inc.
|
||||
SPDX-FileCopyrightText: Feross Aboukhadijeh
|
||||
SPDX-FileCopyrightText: Evan You
|
||||
SPDX-FileCopyrightText: Eduardo San Martin Morote
|
||||
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
|
||||
SPDX-FileCopyrightText: Christoph Wurst
|
||||
SPDX-FileCopyrightText: Austin Andrews
|
||||
|
|
@ -65,6 +68,9 @@ This file is generated from multiple sources. Included packages:
|
|||
- @nextcloud/sharing
|
||||
- version: 0.2.4
|
||||
- license: GPL-3.0-or-later
|
||||
- @vue/devtools-api
|
||||
- version: 6.6.3
|
||||
- license: MIT
|
||||
- base64-js
|
||||
- version: 1.5.1
|
||||
- license: MIT
|
||||
|
|
@ -74,6 +80,9 @@ This file is generated from multiple sources. Included packages:
|
|||
- cancelable-promise
|
||||
- version: 4.3.1
|
||||
- license: MIT
|
||||
- debounce
|
||||
- version: 2.2.0
|
||||
- license: MIT
|
||||
- dompurify
|
||||
- version: 3.2.6
|
||||
- license: (MPL-2.0 OR Apache-2.0)
|
||||
|
|
@ -98,6 +107,9 @@ This file is generated from multiple sources. Included packages:
|
|||
- path
|
||||
- version: 0.12.7
|
||||
- license: MIT
|
||||
- pinia
|
||||
- version: 2.3.1
|
||||
- license: MIT
|
||||
- process
|
||||
- version: 0.11.10
|
||||
- license: MIT
|
||||
|
|
|
|||
2
dist/files_sharing-init-public.js.map
vendored
2
dist/files_sharing-init-public.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
dist/systemtags-init.js
vendored
4
dist/systemtags-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/systemtags-init.js.map
vendored
2
dist/systemtags-init.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +1,2 @@
|
|||
(()=>{"use strict";var e,t,r,o={3360:(e,t,r)=>{var o=r(85168),n=r(32981),a=r(53334);window.addEventListener("DOMContentLoaded",(function(){const{updateLink:e,updateVersion:t}=(0,n.C)("updatenotification","updateState"),r=(0,a.t)("core","{version} is available. Get more information on how to update.",{version:t});(0,o.cf)(r,{onClick:()=>window.open(e,"_blank")})}))}},n={};function a(e){var t=n[e];if(void 0!==t)return t.exports;var r=n[e]={id:e,loaded:!1,exports:{}};return o[e].call(r.exports,r,r.exports,a),r.loaded=!0,r.exports}a.m=o,e=[],a.O=(t,r,o,n)=>{if(!r){var i=1/0;for(u=0;u<e.length;u++){r=e[u][0],o=e[u][1],n=e[u][2];for(var d=!0,l=0;l<r.length;l++)(!1&n||i>=n)&&Object.keys(a.O).every((e=>a.O[e](r[l])))?r.splice(l--,1):(d=!1,n<i&&(i=n));if(d){e.splice(u--,1);var c=o();void 0!==c&&(t=c)}}return t}n=n||0;for(var u=e.length;u>0&&e[u-1][2]>n;u--)e[u]=e[u-1];e[u]=[r,o,n]},a.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var r in t)a.o(t,r)&&!a.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((t,r)=>(a.f[r](e,t),t)),[])),a.u=e=>e+"-"+e+".js?v="+{640:"b2fa23a809053c6305c5",5771:"a4e2a98efcfb7393c5bd",5810:"f63f10359069f886ce52",7432:"bf576075b1d8131aa273"}[e],a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),t={},r="nextcloud:",a.l=(e,o,n,i)=>{if(t[e])t[e].push(o);else{var d,l;if(void 0!==n)for(var c=document.getElementsByTagName("script"),u=0;u<c.length;u++){var s=c[u];if(s.getAttribute("src")==e||s.getAttribute("data-webpack")==r+n){d=s;break}}d||(l=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,a.nc&&d.setAttribute("nonce",a.nc),d.setAttribute("data-webpack",r+n),d.src=e),t[e]=[o];var p=(r,o)=>{d.onerror=d.onload=null,clearTimeout(f);var n=t[e];if(delete t[e],d.parentNode&&d.parentNode.removeChild(d),n&&n.forEach((e=>e(o))),r)return r(o)},f=setTimeout(p.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=p.bind(null,d.onerror),d.onload=p.bind(null,d.onload),l&&document.head.appendChild(d)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=5169,(()=>{var e;a.g.importScripts&&(e=a.g.location+"");var t=a.g.document;if(!e&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){var r=t.getElementsByTagName("script");if(r.length)for(var o=r.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=r[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b=document.baseURI||self.location.href;var e={5169:0};a.f.j=(t,r)=>{var o=a.o(e,t)?e[t]:void 0;if(0!==o)if(o)r.push(o[2]);else{var n=new Promise(((r,n)=>o=e[t]=[r,n]));r.push(o[2]=n);var i=a.p+a.u(t),d=new Error;a.l(i,(r=>{if(a.o(e,t)&&(0!==(o=e[t])&&(e[t]=void 0),o)){var n=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;d.message="Loading chunk "+t+" failed.\n("+n+": "+i+")",d.name="ChunkLoadError",d.type=n,d.request=i,o[1](d)}}),"chunk-"+t,t)}},a.O.j=t=>0===e[t];var t=(t,r)=>{var o,n,i=r[0],d=r[1],l=r[2],c=0;if(i.some((t=>0!==e[t]))){for(o in d)a.o(d,o)&&(a.m[o]=d[o]);if(l)var u=l(a)}for(t&&t(r);c<i.length;c++)n=i[c],a.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return a.O(u)},r=self.webpackChunknextcloud=self.webpackChunknextcloud||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})(),a.nc=void 0;var i=a.O(void 0,[4208],(()=>a(3360)));i=a.O(i)})();
|
||||
//# sourceMappingURL=updatenotification-update-notification-legacy.js.map?v=76c12c6b7f3201af55ae
|
||||
(()=>{"use strict";var e,t,r,o={3360:(e,t,r)=>{var o=r(85168),n=r(32981),a=r(53334);window.addEventListener("DOMContentLoaded",(function(){const{updateLink:e,updateVersion:t}=(0,n.C)("updatenotification","updateState"),r=(0,a.t)("core","{version} is available. Get more information on how to update.",{version:t});(0,o.cf)(r,{onClick:()=>window.open(e,"_blank")})}))}},n={};function a(e){var t=n[e];if(void 0!==t)return t.exports;var r=n[e]={id:e,loaded:!1,exports:{}};return o[e].call(r.exports,r,r.exports,a),r.loaded=!0,r.exports}a.m=o,e=[],a.O=(t,r,o,n)=>{if(!r){var i=1/0;for(u=0;u<e.length;u++){r=e[u][0],o=e[u][1],n=e[u][2];for(var l=!0,c=0;c<r.length;c++)(!1&n||i>=n)&&Object.keys(a.O).every((e=>a.O[e](r[c])))?r.splice(c--,1):(l=!1,n<i&&(i=n));if(l){e.splice(u--,1);var d=o();void 0!==d&&(t=d)}}return t}n=n||0;for(var u=e.length;u>0&&e[u-1][2]>n;u--)e[u]=e[u-1];e[u]=[r,o,n]},a.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var r in t)a.o(t,r)&&!a.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((t,r)=>(a.f[r](e,t),t)),[])),a.u=e=>e+"-"+e+".js?v="+{640:"b2fa23a809053c6305c5",5771:"a4e2a98efcfb7393c5bd",5810:"f63f10359069f886ce52",7432:"bf576075b1d8131aa273"}[e],a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),t={},r="nextcloud:",a.l=(e,o,n,i)=>{if(t[e])t[e].push(o);else{var l,c;if(void 0!==n)for(var d=document.getElementsByTagName("script"),u=0;u<d.length;u++){var s=d[u];if(s.getAttribute("src")==e||s.getAttribute("data-webpack")==r+n){l=s;break}}l||(c=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",r+n),l.src=e),t[e]=[o];var p=(r,o)=>{l.onerror=l.onload=null,clearTimeout(f);var n=t[e];if(delete t[e],l.parentNode&&l.parentNode.removeChild(l),n&&n.forEach((e=>e(o))),r)return r(o)},f=setTimeout(p.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=p.bind(null,l.onerror),l.onload=p.bind(null,l.onload),c&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=5169,(()=>{var e;a.g.importScripts&&(e=a.g.location+"");var t=a.g.document;if(!e&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){var r=t.getElementsByTagName("script");if(r.length)for(var o=r.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=r[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b=document.baseURI||self.location.href;var e={5169:0};a.f.j=(t,r)=>{var o=a.o(e,t)?e[t]:void 0;if(0!==o)if(o)r.push(o[2]);else{var n=new Promise(((r,n)=>o=e[t]=[r,n]));r.push(o[2]=n);var i=a.p+a.u(t),l=new Error;a.l(i,(r=>{if(a.o(e,t)&&(0!==(o=e[t])&&(e[t]=void 0),o)){var n=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;l.message="Loading chunk "+t+" failed.\n("+n+": "+i+")",l.name="ChunkLoadError",l.type=n,l.request=i,o[1](l)}}),"chunk-"+t,t)}},a.O.j=t=>0===e[t];var t=(t,r)=>{var o,n,i=r[0],l=r[1],c=r[2],d=0;if(i.some((t=>0!==e[t]))){for(o in l)a.o(l,o)&&(a.m[o]=l[o]);if(c)var u=c(a)}for(t&&t(r);d<i.length;d++)n=i[d],a.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return a.O(u)},r=self.webpackChunknextcloud=self.webpackChunknextcloud||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})(),a.nc=void 0;var i=a.O(void 0,[4208],(()=>a(3360)));i=a.O(i)})();
|
||||
//# sourceMappingURL=updatenotification-update-notification-legacy.js.map?v=4c377cdebe46c2279c41
|
||||
File diff suppressed because one or more lines are too long
4
dist/user_status-menu.js
vendored
4
dist/user_status-menu.js
vendored
File diff suppressed because one or more lines are too long
2
dist/user_status-menu.js.map
vendored
2
dist/user_status-menu.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/weather_status-weather-status.js
vendored
4
dist/weather_status-weather-status.js
vendored
File diff suppressed because one or more lines are too long
2
dist/weather_status-weather-status.js.map
vendored
2
dist/weather_status-weather-status.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue