Merge pull request #58208 from nextcloud/chore/update-files-4rc3

chore: update `@nextcloud/files` to v4.0.0-rc.3
This commit is contained in:
Ferdinand Thiessen 2026-02-10 15:22:34 +01:00 committed by GitHub
commit 1a5679b176
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
108 changed files with 920 additions and 901 deletions

View file

@ -115,6 +115,7 @@ import FileEntryActions from './FileEntry/FileEntryActions.vue'
import FileEntryCheckbox from './FileEntry/FileEntryCheckbox.vue'
import FileEntryName from './FileEntry/FileEntryName.vue'
import FileEntryPreview from './FileEntry/FileEntryPreview.vue'
import { useFileActions } from '../composables/useFileActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
@ -170,7 +171,10 @@ export default defineComponent({
activeView,
} = useActiveStore()
const actions = useFileActions()
return {
actions,
actionsMenuStore,
activeFolder,
activeNode,

View file

@ -77,6 +77,7 @@ import FileEntryActions from './FileEntry/FileEntryActions.vue'
import FileEntryCheckbox from './FileEntry/FileEntryCheckbox.vue'
import FileEntryName from './FileEntry/FileEntryName.vue'
import FileEntryPreview from './FileEntry/FileEntryPreview.vue'
import { useFileActions } from '../composables/useFileActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
@ -122,7 +123,10 @@ export default defineComponent({
activeView,
} = useActiveStore()
const actions = useFileActions()
return {
actions,
actionsMenuStore,
activeFolder,
activeNode,

View file

@ -8,7 +8,7 @@ import type { PropType } from 'vue'
import type { FileSource } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { FileType, Folder, getFileActions, File as NcFile, Node, NodeStatus, Permission } from '@nextcloud/files'
import { FileType, Folder, File as NcFile, Node, NodeStatus, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'
@ -24,8 +24,6 @@ import { isDownloadable } from '../utils/permissions.ts'
Vue.directive('onClickOutside', vOnClickOutside)
const actions = getFileActions()
export default defineComponent({
props: {
source: {
@ -233,7 +231,7 @@ export default defineComponent({
return []
}
return actions
return this.actions
.filter((action: IFileAction) => {
if (!action.enabled) {
return true

View file

@ -6,20 +6,22 @@
<script setup lang="ts">
import type { IHotkeyConfig } from '@nextcloud/files'
import { getFileActions } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { computed } from 'vue'
import NcAppSettingsShortcutsSection from '@nextcloud/vue/components/NcAppSettingsShortcutsSection'
import NcHotkey from '@nextcloud/vue/components/NcHotkey'
import NcHotkeyList from '@nextcloud/vue/components/NcHotkeyList'
import { useFileActions } from '../../composables/useFileActions.ts'
const actionHotkeys = getFileActions()
const actions = useFileActions()
const actionHotkeys = computed(() => actions.value
.filter((action) => !!action.hotkey)
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map((action) => ({
id: action.id,
label: action.hotkey!.description,
hotkey: hotkeyToString(action.hotkey!),
}))
})))
/**
* Convert a hotkey configuration to a hotkey string.

View file

@ -9,7 +9,7 @@
</template>
<script lang="ts">
import type { Folder, Header, View } from '@nextcloud/files'
import type { Folder, IFileListHeader, View } from '@nextcloud/files'
import type { PropType } from 'vue'
import PQueue from 'p-queue'
@ -25,7 +25,7 @@ export default {
name: 'FilesListHeader',
props: {
header: {
type: Object as PropType<Header>,
type: Object as PropType<IFileListHeader>,
required: true,
},

View file

@ -12,7 +12,7 @@
:disabled="!!loading || areSomeNodesLoading"
:force-name="true"
:inline="enabledInlineActions.length"
:menu-name="enabledInlineActions.length <= 1 ? t('files', 'Actions') : null"
:menu-name="enabledInlineActions.length <= 1 ? t('files', 'Actions') : undefined"
@close="openedSubmenu = null">
<!-- Default actions list-->
<NcActionButton
@ -75,7 +75,7 @@ import type { PropType } from 'vue'
import type { FileSource } from '../types.ts'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { DefaultType, getFileActions, NodeStatus } from '@nextcloud/files'
import { DefaultType, NodeStatus } from '@nextcloud/files'
import { translate } from '@nextcloud/l10n'
import { computed, defineComponent } from 'vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
@ -84,6 +84,7 @@ import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import { useFileActions } from '../composables/useFileActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import logger from '../logger.ts'
import actionsMixins from '../mixins/actionsMixin.ts'
@ -92,9 +93,6 @@ import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
// The registered actions list
const actions = getFileActions()
export default defineComponent({
name: 'FilesListTableHeaderActions',
@ -128,7 +126,7 @@ export default defineComponent({
const selectionStore = useSelectionStore()
const { isMedium, isNarrow } = useFileListWidth()
const boundariesElement = document.getElementById('app-content-vue')
const boundariesElement = document.getElementById('app-content-vue') as HTMLElement
const inlineActions = computed(() => {
if (isNarrow.value) {
@ -140,7 +138,10 @@ export default defineComponent({
return 3
})
const actions = useFileActions()
return {
actions,
actionsMenuStore,
activeFolder,
filesStore,
@ -159,7 +160,7 @@ export default defineComponent({
computed: {
enabledFileActions(): IFileAction[] {
return actions
return this.actions
// We don't handle renderInline actions in this component
.filter((action) => !action.renderInline)
// We don't handle actions that are not visible

View file

@ -74,7 +74,7 @@ import type { ComponentPublicInstance, PropType } from 'vue'
import type { UserConfig } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { FileType, Folder, getFileActions, getSidebar, Permission, View } from '@nextcloud/files'
import { FileType, Folder, getSidebar, Permission, View } from '@nextcloud/files'
import { n, t } from '@nextcloud/l10n'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { computed, defineComponent } from 'vue'
@ -87,6 +87,7 @@ import FilesListTableFooter from './FilesListTableFooter.vue'
import FilesListTableHeader from './FilesListTableHeader.vue'
import FilesListTableHeaderActions from './FilesListTableHeaderActions.vue'
import VirtualList from './VirtualList.vue'
import { useEnabledFileActions } from '../composables/useFileActions.ts'
import { useFileListHeaders } from '../composables/useFileListHeaders.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
@ -362,21 +363,13 @@ export default defineComponent({
}
if (node.type === FileType.File) {
const defaultAction = getFileActions()
// Get only default actions (visible and hidden)
.filter((action) => !!action?.default)
// Find actions that are either always enabled or enabled for the current node
.filter((action) => (!action.enabled || action.enabled({
nodes: [node],
view: this.currentView,
folder: this.currentFolder,
contents: this.nodes,
})))
.filter((action) => action.id !== 'download')
// Sort enabled default actions by order
.sort((a, b) => (a.order || 0) - (b.order || 0))
// Get the first one
.at(0)
const actions = useEnabledFileActions({
nodes: [node],
view: this.currentView,
folder: this.currentFolder,
contents: this.nodes,
})
const defaultAction = actions.value.find((action) => action.id !== 'download' && !!action.default)
// Some file types do not have a default action (e.g. they can only be downloaded)
// So if there is an enabled default action, so execute it

View file

@ -0,0 +1,124 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction, INode, registerFileAction } from '@nextcloud/files'
import type * as composable from './useFileActions.ts'
import { Folder, View } from '@nextcloud/files'
import { defaultRemoteURL, defaultRootPath } from '@nextcloud/files/dav'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'
interface Context {
useFileActions: typeof composable.useFileActions
useEnabledFileActions: typeof composable.useEnabledFileActions
registerFileAction: typeof registerFileAction
}
describe('useFileActions', () => {
beforeEach(async (context: Context) => {
delete globalThis._nc_files_scope
// reset modules to reset internal variables (the headers ref) of the composable and the library (the scoped globals)
vi.resetModules()
context.useFileActions = (await import('./useFileActions.ts')).useFileActions
context.useEnabledFileActions = (await import('./useFileActions.ts')).useEnabledFileActions
context.registerFileAction = (await import('@nextcloud/files')).registerFileAction
})
it<Context>('gets the actions', ({ useFileActions, registerFileAction }) => {
const action: IFileAction = { id: '1', order: 5, displayName: () => 'Action', iconSvgInline: vi.fn(), exec: vi.fn() }
registerFileAction(action)
const actions = useFileActions()
expect(actions.value).toEqual([action])
})
it<Context>('composable is reactive', async ({ useFileActions, registerFileAction }) => {
const action: IFileAction = { id: '1', order: 5, displayName: () => 'Action', iconSvgInline: vi.fn(), exec: vi.fn() }
registerFileAction(action)
await nextTick()
const actions = useFileActions()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1'])
// now add a new action
const action2: IFileAction = { id: '2', order: 9, displayName: () => 'Action', iconSvgInline: vi.fn(), exec: vi.fn() }
registerFileAction(action2)
// reactive update, lower order first
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '2'])
})
})
describe('useEnabledFileActions', () => {
beforeEach(async (context: Context) => {
delete globalThis._nc_files_scope
// reset modules to reset internal variables (the headers ref) of the composable and the library (the scoped globals)
vi.resetModules()
context.useFileActions = (await import('./useFileActions.ts')).useFileActions
context.useEnabledFileActions = (await import('./useFileActions.ts')).useEnabledFileActions
context.registerFileAction = (await import('@nextcloud/files')).registerFileAction
})
it<Context>('gets the actions', ({ useEnabledFileActions, registerFileAction }) => {
registerFileAction({ id: '1', order: 0, displayName: () => 'Action 1', iconSvgInline: vi.fn(), exec: vi.fn() })
registerFileAction({ id: '2', order: 5, displayName: () => 'Action 2', enabled: () => false, iconSvgInline: vi.fn(), exec: vi.fn() })
registerFileAction({ id: '3', order: 9, displayName: () => 'Action 3', enabled: () => true, iconSvgInline: vi.fn(), exec: vi.fn() })
const folder = new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath })
const view = new View({ id: 'view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' })
const contents = []
const actions = useEnabledFileActions({ folder, contents, view })
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '3'])
})
it<Context>('composable is reactive', async ({ useEnabledFileActions, registerFileAction }) => {
registerFileAction({ id: '1', order: 0, displayName: () => 'Action 1', iconSvgInline: vi.fn(), exec: vi.fn() })
registerFileAction({ id: '2', order: 5, displayName: () => 'Action 2', enabled: () => false, iconSvgInline: vi.fn(), exec: vi.fn() })
const folder = new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath })
const view = new View({ id: 'view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' })
const contents = []
const actions = useEnabledFileActions({ folder, contents, view })
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1'])
registerFileAction({ id: '3', order: 9, displayName: () => 'Action 3', enabled: () => true, iconSvgInline: vi.fn(), exec: vi.fn() })
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '3'])
})
it<Context>('composable is reactive to context changes', async ({ useEnabledFileActions, registerFileAction }) => {
// only enabled if view id === 'enabled-view'
registerFileAction({ id: '1', order: 0, displayName: () => 'Action 1', enabled: ({ view }) => view.id === 'enabled-view', iconSvgInline: vi.fn(), exec: vi.fn() })
// only enabled if contents has items
registerFileAction({ id: '2', order: 5, displayName: () => 'Action 2', enabled: ({ contents }) => contents.length > 0, iconSvgInline: vi.fn(), exec: vi.fn() })
// only enabled if folder owner is 'owner2'
registerFileAction({ id: '3', order: 9, displayName: () => 'Action 3', enabled: ({ folder }) => folder.owner === 'owner2', iconSvgInline: vi.fn(), exec: vi.fn() })
const context = ref({
folder: new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath }),
view: new View({ id: 'disabled-view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' }),
contents: ref<INode[]>([(new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath }))]),
})
const actions = useEnabledFileActions(context)
// we have contents but wrong folder and view so only 2 is enabled
expect(actions.value.map(({ id }) => id)).toStrictEqual(['2'])
// no contents so nothing is enabled
context.value.contents = []
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual([])
// correct owner for action 3
context.value.folder = new Folder({ owner: 'owner2', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath })
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['3'])
// correct view for action 1
context.value.view = new View({ id: 'enabled-view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' })
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '3'])
})
})

View file

@ -0,0 +1,42 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ActionContext, IFileAction } from '@nextcloud/files'
import type { MaybeRefOrGetter } from '@vueuse/core'
import type { Ref } from 'vue'
import { getFileActions, getFilesRegistry } from '@nextcloud/files'
import { toValue } from '@vueuse/core'
import { computed, readonly, ref } from 'vue'
const actions = ref<IFileAction[] | undefined>()
/**
* Get the registered and sorted file actions.
*/
export function useFileActions() {
if (!actions.value) {
// if not initialized by other component yet, initialize and subscribe to registry changes
actions.value = getFileActions()
getFilesRegistry().addEventListener('register:action', () => {
actions.value = getFileActions()
})
}
return readonly(actions as Ref<IFileAction[]>)
}
/**
* Get the enabled file actions for the given context.
*
* @param context - The context to check the enabled state of the actions against
*/
export function useEnabledFileActions(context: MaybeRefOrGetter<ActionContext>) {
const actions = useFileActions()
return computed(() => actions.value
.filter((action) => action.enabled === undefined
|| action.enabled(toValue(context)!))
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)))
}

View file

@ -0,0 +1,133 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileListAction, INode, registerFileListAction } from '@nextcloud/files'
import type * as composable from './useFileListActions.ts'
import { Folder, View } from '@nextcloud/files'
import { defaultRemoteURL, defaultRootPath } from '@nextcloud/files/dav'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref, shallowRef } from 'vue'
interface Context {
useFileListActions: typeof composable.useFileListActions
useEnabledFileListActions: typeof composable.useEnabledFileListActions
registerFileListAction: typeof registerFileListAction
}
describe('useFileListActions', () => {
beforeEach(async (context: Context) => {
delete globalThis._nc_files_scope
// reset modules to reset internal variables (the headers ref) of the composable and the library (the scoped globals)
vi.resetModules()
context.useFileListActions = (await import('./useFileListActions.ts')).useFileListActions
context.useEnabledFileListActions = (await import('./useFileListActions.ts')).useEnabledFileListActions
context.registerFileListAction = (await import('@nextcloud/files')).registerFileListAction
})
it<Context>('gets the actions', ({ useFileListActions, registerFileListAction }) => {
const action: IFileListAction = { id: '1', order: 5, displayName: () => 'Action', exec: vi.fn() }
registerFileListAction(action)
const actions = useFileListActions()
expect(actions.value).toEqual([action])
})
it<Context>('actions are sorted', ({ useFileListActions, registerFileListAction }) => {
const action: IFileListAction = { id: '1', order: 5, displayName: () => 'Action 1', exec: vi.fn() }
const action2: IFileListAction = { id: '2', order: 0, displayName: () => 'Action 2', exec: vi.fn() }
registerFileListAction(action)
registerFileListAction(action2)
const actions = useFileListActions()
// lower order first
expect(actions.value.map(({ id }) => id)).toStrictEqual(['2', '1'])
})
it<Context>('composable is reactive', async ({ useFileListActions, registerFileListAction }) => {
const action: IFileListAction = { id: '1', order: 5, displayName: () => 'Action', exec: vi.fn() }
registerFileListAction(action)
await nextTick()
const actions = useFileListActions()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1'])
// now add a new action
const action2: IFileListAction = { id: '2', order: 0, displayName: () => 'Action', exec: vi.fn() }
registerFileListAction(action2)
// reactive update, lower order first
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['2', '1'])
})
})
describe('useEnabledFileListActions', () => {
beforeEach(async (context: Context) => {
delete globalThis._nc_files_scope
// reset modules to reset internal variables (the headers ref) of the composable and the library (the scoped globals)
vi.resetModules()
context.useFileListActions = (await import('./useFileListActions.ts')).useFileListActions
context.useEnabledFileListActions = (await import('./useFileListActions.ts')).useEnabledFileListActions
context.registerFileListAction = (await import('@nextcloud/files')).registerFileListAction
})
it<Context>('gets the actions sorted', ({ useEnabledFileListActions, registerFileListAction }) => {
registerFileListAction({ id: '1', order: 0, displayName: () => 'Action 1', exec: vi.fn() })
registerFileListAction({ id: '2', order: 5, displayName: () => 'Action 2', enabled: () => false, exec: vi.fn() })
registerFileListAction({ id: '3', order: 9, displayName: () => 'Action 3', enabled: () => true, exec: vi.fn() })
const folder = new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath })
const view = new View({ id: 'view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' })
const contents = []
const actions = useEnabledFileListActions(folder, contents, view)
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '3'])
})
it<Context>('composable is reactive', async ({ useEnabledFileListActions, registerFileListAction }) => {
registerFileListAction({ id: '1', order: 0, displayName: () => 'Action 1', exec: vi.fn() })
registerFileListAction({ id: '2', order: 5, displayName: () => 'Action 2', enabled: () => false, exec: vi.fn() })
const folder = new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath })
const view = new View({ id: 'view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' })
const contents = []
const actions = useEnabledFileListActions(folder, contents, view)
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1'])
registerFileListAction({ id: '3', order: 9, displayName: () => 'Action 3', enabled: () => true, exec: vi.fn() })
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '3'])
})
it<Context>('composable is reactive to context changes', async ({ useEnabledFileListActions, registerFileListAction }) => {
// only enabled if view id === 'enabled-view'
registerFileListAction({ id: '1', order: 0, displayName: () => 'Action 1', enabled: ({ view }) => view.id === 'enabled-view', exec: vi.fn() })
// only enabled if contents has items
registerFileListAction({ id: '2', order: 5, displayName: () => 'Action 2', enabled: ({ contents }) => contents.length > 0, exec: vi.fn() })
// only enabled if folder owner is 'owner2'
registerFileListAction({ id: '3', order: 9, displayName: () => 'Action 3', enabled: ({ folder }) => folder.owner === 'owner2', exec: vi.fn() })
const folder = shallowRef(new Folder({ owner: 'owner', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath }))
const view = shallowRef(new View({ id: 'disabled-view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' }))
const contents = ref<INode[]>([folder.value])
const actions = useEnabledFileListActions(folder, contents, view)
// we have contents but wrong folder and view so only 2 is enabled
expect(actions.value.map(({ id }) => id)).toStrictEqual(['2'])
// no contents so nothing is enabled
contents.value = []
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual([])
// correct owner for action 3
folder.value = new Folder({ owner: 'owner2', root: defaultRootPath, source: defaultRemoteURL + defaultRootPath })
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['3'])
// correct view for action 1
view.value = new View({ id: 'enabled-view', getContents: vi.fn(), icon: '<svg></svg>', name: 'View' })
await nextTick()
expect(actions.value.map(({ id }) => id)).toStrictEqual(['1', '3'])
})
})

View file

@ -0,0 +1,53 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileListAction, IFolder, INode, IView } from '@nextcloud/files'
import type { MaybeRefOrGetter } from '@vueuse/core'
import type { ComputedRef } from 'vue'
import { getFileListActions, getFilesRegistry } from '@nextcloud/files'
import { toValue } from '@vueuse/core'
import { computed, ref } from 'vue'
const actions = ref<IFileListAction[]>()
const sorted = computed(() => [...(actions.value ?? [])].sort((a, b) => a.order - b.order))
/**
* Get the registered and sorted file list actions.
*/
export function useFileListActions(): ComputedRef<IFileListAction[]> {
if (!actions.value) {
// if not initialized by other component yet, initialize and subscribe to registry changes
actions.value = getFileListActions()
getFilesRegistry().addEventListener('register:listAction', () => {
actions.value = getFileListActions()
})
}
return sorted
}
/**
* Get the enabled file list actions for the given folder, contents and view.
*
* @param folder - The current folder
* @param contents - The contents of the current folder
* @param view - The current view
*/
export function useEnabledFileListActions(
folder: MaybeRefOrGetter<IFolder | undefined>,
contents: MaybeRefOrGetter<INode[]>,
view: MaybeRefOrGetter<IView | undefined>,
) {
const actions = useFileListActions()
return computed(() => {
if (toValue(folder) === undefined || toValue(view) === undefined) {
return []
}
return actions.value.filter((action) => action.enabled === undefined
|| action.enabled({ folder: toValue(folder)!, contents: toValue(contents), view: toValue(view)! }))
})
}

View file

@ -3,39 +3,59 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { Header } from '@nextcloud/files'
import type { IFileListHeader } from '@nextcloud/files'
import type { registerFileListHeader } from '@nextcloud/files'
import type { ComputedRef } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useFileListHeaders } from './useFileListHeaders.ts'
import { nextTick } from 'vue'
const getFileListHeaders = vi.hoisted(() => vi.fn())
vi.mock('@nextcloud/files', async (originalModule) => {
return {
...(await originalModule()),
getFileListHeaders,
}
})
interface Context {
useFileListHeaders: () => ComputedRef<IFileListHeader[]>
registerFileListHeader: typeof registerFileListHeader
}
describe('useFileListHeaders', () => {
beforeEach(() => vi.resetAllMocks())
beforeEach(async (context: Context) => {
delete globalThis._nc_files_scope
// reset modules to reset internal variables (the headers ref) of the composable and the library (the scoped globals)
vi.resetModules()
context.useFileListHeaders = (await import('./useFileListHeaders.ts')).useFileListHeaders
context.registerFileListHeader = (await import('@nextcloud/files')).registerFileListHeader
})
it('gets the headers', () => {
const header = new Header({ id: '1', order: 5, render: vi.fn(), updated: vi.fn() })
getFileListHeaders.mockImplementationOnce(() => [header])
it<Context>('gets the headers', ({ useFileListHeaders, registerFileListHeader }) => {
const header: IFileListHeader = { id: '1', order: 5, render: vi.fn(), updated: vi.fn() }
registerFileListHeader(header)
const headers = useFileListHeaders()
expect(headers.value).toEqual([header])
expect(getFileListHeaders).toHaveBeenCalledOnce()
})
it('headers are sorted', () => {
const header = new Header({ id: '1', order: 10, render: vi.fn(), updated: vi.fn() })
const header2 = new Header({ id: '2', order: 5, render: vi.fn(), updated: vi.fn() })
getFileListHeaders.mockImplementationOnce(() => [header, header2])
it<Context>('headers are sorted', ({ useFileListHeaders, registerFileListHeader }) => {
const header: IFileListHeader = { id: '1', order: 10, render: vi.fn(), updated: vi.fn() }
const header2: IFileListHeader = { id: '2', order: 5, render: vi.fn(), updated: vi.fn() }
registerFileListHeader(header)
registerFileListHeader(header2)
const headers = useFileListHeaders()
// lower order first
expect(headers.value.map(({ id }) => id)).toStrictEqual(['2', '1'])
expect(getFileListHeaders).toHaveBeenCalledOnce()
})
it<Context>('composable is reactive', async ({ useFileListHeaders, registerFileListHeader }) => {
const header: IFileListHeader = { id: 'a', order: 10, render: vi.fn(), updated: vi.fn() }
registerFileListHeader(header)
await nextTick()
const headers = useFileListHeaders()
expect(headers.value.map(({ id }) => id)).toStrictEqual(['a'])
// now add a new header
const header2: IFileListHeader = { id: 'b', order: 5, render: vi.fn(), updated: vi.fn() }
registerFileListHeader(header2)
// reactive update, lower order first
await nextTick()
expect(headers.value.map(({ id }) => id)).toStrictEqual(['b', 'a'])
})
})

View file

@ -2,18 +2,27 @@
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Header } from '@nextcloud/files'
import type { IFileListHeader } from '@nextcloud/files'
import type { ComputedRef } from 'vue'
import { getFileListHeaders } from '@nextcloud/files'
import { getFileListHeaders, getFilesRegistry } from '@nextcloud/files'
import { computed, ref } from 'vue'
const headers = ref<IFileListHeader[]>()
const sorted = computed(() => [...(headers.value ?? [])].sort((a, b) => a.order - b.order) as IFileListHeader[])
/**
* Get the registered and sorted file list headers.
*/
export function useFileListHeaders(): ComputedRef<Header[]> {
const headers = ref(getFileListHeaders())
const sorted = computed(() => [...headers.value].sort((a, b) => a.order - b.order) as Header[])
export function useFileListHeaders(): ComputedRef<IFileListHeader[]> {
if (!headers.value) {
// if not initialized by other component yet, initialize and subscribe to registry changes
headers.value = getFileListHeaders()
getFilesRegistry().addEventListener('register:listHeader', () => {
headers.value = getFileListHeaders()
})
}
return sorted
}

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { addNewFileMenuEntry, registerFileAction } from '@nextcloud/files'
import { addNewFileMenuEntry, getNewFileMenu, registerFileAction } from '@nextcloud/files'
import { registerDavProperty } from '@nextcloud/files/dav'
import { isPublicShare } from '@nextcloud/sharing/public'
import { registerConvertActions } from './actions/convertAction.ts'
@ -79,3 +79,14 @@ registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:metadata-blurhash', { nc: 'http://nextcloud.org/ns' })
initLivePhotos()
// TODO: REMOVE THIS ONCE THE UPLOAD LIBRARY IS MIGRATED TO THE NEW FILES LIBRARY
window._nc_newfilemenu = new Proxy(getNewFileMenu(), {
get(target, prop) {
return target[prop as keyof typeof target]
},
set(target, prop, value) {
target[prop as keyof typeof target] = value
return true
},
})

View file

@ -1,8 +1,9 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { NewMenuEntry, Node } from '@nextcloud/files'
import type { IFolder, INode, NewMenuEntry } from '@nextcloud/files'
import FolderPlusSvg from '@mdi/svg/svg/folder-plus-outline.svg?raw'
import { getCurrentUser } from '@nextcloud/auth'
@ -10,48 +11,24 @@ import axios from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { Folder, Permission } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { t } from '@nextcloud/l10n'
import { basename } from 'path'
import logger from '../logger.ts'
import { newNodeName } from '../utils/newNodeDialog.ts'
type createFolderResponse = {
fileid: number
source: string
}
/**
*
* @param root
* @param name
*/
async function createNewFolder(root: Folder, name: string): Promise<createFolderResponse> {
const source = root.source + '/' + name
const encodedSource = root.encodedSource + '/' + encodeURIComponent(name)
const response = await axios({
method: 'MKCOL',
url: encodedSource,
headers: {
Overwrite: 'F',
},
})
return {
fileid: parseInt(response.headers['oc-fileid']),
source,
}
}
export const entry: NewMenuEntry = {
id: 'newFolder',
order: 0,
displayName: t('files', 'New folder'),
enabled: (context: Folder) => Boolean(context.permissions & Permission.CREATE) && Boolean(context.permissions & Permission.READ),
// Make the svg icon color match the primary element color
iconSvgInline: FolderPlusSvg.replace(/viewBox/gi, 'style="color: var(--color-primary-element)" viewBox'),
order: 0,
async handler(context: Folder, content: Node[]) {
enabled(context) {
return Boolean(context.permissions & Permission.CREATE)
&& Boolean(context.permissions & Permission.READ)
},
async handler(context: IFolder, content: INode[]) {
const name = await newNodeName(t('files', 'New folder'), content)
if (name === null) {
return
@ -92,3 +69,31 @@ export const entry: NewMenuEntry = {
}
},
}
type createFolderResponse = {
fileid: number
source: string
}
/**
* Create a new folder in the given root with the given name
*
* @param root - The folder in which the new folder should be created
* @param name - The name of the new folder
*/
async function createNewFolder(root: IFolder, name: string): Promise<createFolderResponse> {
const source = root.source + '/' + name
const encodedSource = root.encodedSource + '/' + encodeURIComponent(name)
const response = await axios({
method: 'MKCOL',
url: encodedSource,
headers: {
Overwrite: 'F',
},
})
return {
fileid: parseInt(response.headers['oc-fileid']),
source,
}
}

View file

@ -1,15 +1,30 @@
/**
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction, IFolder, INode, IView } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { subscribe } from '@nextcloud/event-bus'
import { getNavigation } from '@nextcloud/files'
import { Folder, getNavigation, Permission } from '@nextcloud/files'
import { getRemoteURL, getRootPath } from '@nextcloud/files/dav'
import { defineStore } from 'pinia'
import { ref, shallowRef, watch } from 'vue'
import { computed, ref, shallowRef, watch } from 'vue'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import logger from '../logger.ts'
import { useFilesStore } from './files.ts'
// Temporary fake folder to use until we have the first valid folder
// fetched and cached. This allow us to mount the FilesListVirtual
// at all time and avoid unmount/mount and undesired rendering issues.
const dummyFolder = new Folder({
id: 0,
source: getRemoteURL() + getRootPath(),
root: getRootPath(),
owner: getCurrentUser()?.uid || null,
permissions: Permission.NONE,
})
export const useActiveStore = defineStore('active', () => {
/**
@ -17,11 +32,6 @@ export const useActiveStore = defineStore('active', () => {
*/
const activeAction = shallowRef<IFileAction>()
/**
* The currently active folder
*/
const activeFolder = ref<IFolder>()
/**
* The current active node within the folder
*/
@ -32,6 +42,20 @@ export const useActiveStore = defineStore('active', () => {
*/
const activeView = shallowRef<IView>()
const filesStore = useFilesStore()
const { directory } = useRouteParameters()
/**
* The currently active folder
*/
const activeFolder = computed<IFolder>(() => {
if (!activeView.value?.id) {
return dummyFolder
}
return filesStore.getDirectoryByPath(activeView.value.id, directory.value)
?? dummyFolder
})
// Set the active node on the router params
watch(activeNode, () => {
if (typeof activeNode.value?.fileid !== 'number' || activeNode.value.fileid === activeFolder.value?.fileid) {

View file

@ -6,7 +6,7 @@
import type { FilterUpdateChipsEvent, IFileListFilter, IFileListFilterChip, IFileListFilterWithUi } from '@nextcloud/files'
import { emit, subscribe } from '@nextcloud/event-bus'
import { getFileListFilters } from '@nextcloud/files'
import { getFileListFilters, getFilesRegistry } from '@nextcloud/files'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import logger from '../logger.ts'
@ -63,8 +63,8 @@ export const useFiltersStore = defineStore('filters', () => {
const index = filters.value.findIndex(({ id }) => id === filterId)
if (index > -1) {
const [filter] = filters.value.splice(index, 1)
filter.removeEventListener('update:chips', onFilterUpdateChips)
filter.removeEventListener('update:filter', onFilterUpdate)
filter!.removeEventListener('update:chips', onFilterUpdateChips)
filter!.removeEventListener('update:filter', onFilterUpdate)
logger.debug('Files list filter unregistered', { id: filterId })
}
}
@ -92,27 +92,7 @@ export const useFiltersStore = defineStore('filters', () => {
logger.debug('File list filter chips updated', { filter: id, chips: event.detail })
}
/**
* Event handler that resets all filters if the file list view was changed.
*
*/
function onViewChanged() {
logger.debug('Reset all file list filters - view changed')
for (const filter of filters.value) {
if (filter.reset !== undefined) {
filter.reset()
}
}
}
// Initialize the store
subscribe('files:navigation:changed', onViewChanged)
subscribe('files:filter:added', addFilter)
subscribe('files:filter:removed', removeFilter)
for (const filter of getFileListFilters()) {
addFilter(filter)
}
initialize()
return {
// state
@ -123,9 +103,44 @@ export const useFiltersStore = defineStore('filters', () => {
// getters / computed
activeChips,
sortedFilters,
}
// actions / methods
addFilter,
removeFilter,
/**
* Initialize the store by registering event listeners and loading initial filters.
*
* @internal
*/
function initialize() {
const registry = getFilesRegistry()
const initialFilters = getFileListFilters()
// handle adding and removing filters after initialization
registry.addEventListener('register:listFilter', (event) => {
addFilter(event.detail)
})
registry.addEventListener('unregister:listFilter', (event) => {
removeFilter(event.detail)
})
// register the initial filters
for (const filter of initialFilters) {
addFilter(filter)
}
// subscribe to file list view changes to reset the filters
subscribe('files:navigation:changed', onViewChanged)
}
/**
* Event handler that resets all filters if the file list view was changed.
*
* @internal
*/
function onViewChanged() {
logger.debug('Reset all file list filters - view changed')
for (const filter of filters.value) {
if (filter.reset !== undefined) {
filter.reset()
}
}
}
})

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { INode } from '@nextcloud/files'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import NewNodeDialog from '../components/NewNodeDialog.vue'
@ -27,8 +27,8 @@ interface ILabels {
* @param labels Labels to set on the dialog
* @return string if successful otherwise null if aborted
*/
export function newNodeName(defaultName: string, folderContent: Node[], labels: ILabels = {}) {
const contentNames = folderContent.map((node: Node) => node.basename)
export function newNodeName(defaultName: string, folderContent: INode[], labels: ILabels = {}) {
const contentNames = folderContent.map((node: INode) => node.basename)
return new Promise<string | null>((resolve) => {
spawnDialog(NewNodeDialog, {

View file

@ -160,11 +160,9 @@ import type { ComponentPublicInstance } from 'vue'
import type { Route } from 'vue-router'
import type { UserConfig } from '../types.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { showError, showSuccess, showWarning } from '@nextcloud/dialogs'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { Folder, getFileListActions, Permission, sortNodes } from '@nextcloud/files'
import { getRemoteURL, getRootPath } from '@nextcloud/files/dav'
import { Permission, sortNodes } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import { dirname, join } from '@nextcloud/paths'
@ -189,6 +187,7 @@ import BreadCrumbs from '../components/BreadCrumbs.vue'
import DragAndDropNotice from '../components/DragAndDropNotice.vue'
import FileListFilters from '../components/FileListFilter/FileListFilters.vue'
import FilesListVirtual from '../components/FilesListVirtual.vue'
import { useEnabledFileListActions } from '../composables/useFileListActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import logger from '../logger.ts'
@ -258,13 +257,27 @@ export default defineComponent({
const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', [])
const currentView = computed(() => activeStore.activeView)
const currentFolder = computed(() => activeStore.activeFolder)
const dirContents = computed<INode[]>(() => {
const sources = (currentFolder.value as { _children?: string[] })?._children ?? []
return sources.map(filesStore.getNode)
.filter(Boolean) as INode[]
})
const enabledFileListActions = useEnabledFileListActions(
currentFolder,
dirContents,
currentView,
)
return {
currentFolder,
currentView,
dirContents,
directory,
enabledFileListActions,
fileId,
isNarrow,
t,
sidebar,
activeStore,
@ -280,6 +293,7 @@ export default defineComponent({
enableGridView,
forbiddenCharacters,
ShareType,
t,
}
},
@ -329,34 +343,6 @@ export default defineComponent({
return `${this.currentFolder.displayname} - ${title}`
},
/**
* The current folder.
*/
currentFolder(): Folder {
// Temporary fake folder to use until we have the first valid folder
// fetched and cached. This allow us to mount the FilesListVirtual
// at all time and avoid unmount/mount and undesired rendering issues.
const dummyFolder = new Folder({
id: 0,
source: getRemoteURL() + getRootPath(),
root: getRootPath(),
owner: getCurrentUser()?.uid || null,
permissions: Permission.NONE,
})
if (!this.currentView?.id) {
return dummyFolder
}
return this.filesStore.getDirectoryByPath(this.currentView.id, this.directory) || dummyFolder
},
dirContents(): Node[] {
return (this.currentFolder?._children || [])
.map(this.filesStore.getNode)
.filter((node: Node) => !!node)
},
/**
* The current directory contents.
*/
@ -445,27 +431,6 @@ export default defineComponent({
return !this.loading && this.isEmptyDir && this.currentView?.emptyView !== undefined
},
enabledFileListActions() {
if (!this.currentView || !this.currentFolder) {
return []
}
const actions = getFileListActions()
const enabledActions = actions
.filter((action) => {
if (action.enabled === undefined) {
return true
}
return action.enabled({
view: this.currentView!,
folder: this.currentFolder!,
contents: this.dirContents,
})
})
.toSorted((a, b) => a.order - b.order)
return enabledActions
},
/**
* Using the filtered content if filters are active
*/
@ -495,10 +460,6 @@ export default defineComponent({
}
},
currentFolder() {
this.activeStore.activeFolder = this.currentFolder
},
currentView(newView, oldView) {
if (newView?.id === oldView?.id) {
return

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Folder, Navigation } from '@nextcloud/files'
import type { IFolder } from '@nextcloud/files'
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
import { getNavigation, View } from '@nextcloud/files'
@ -21,13 +21,21 @@ beforeAll(async () => {
await fireEvent.resize(window)
})
const navigation = getNavigation()
beforeEach(() => {
const views = [...navigation.views]
for (const view of views) {
navigation.remove(view.id)
}
expect(navigation.views).toHaveLength(0)
})
describe('Navigation', () => {
beforeEach(cleanup)
beforeEach(async () => {
delete window._nc_navigation
mockWindow()
getNavigation().register(createView('files', 'Files'))
navigation.register(createView('files', 'Files'))
await router.replace({ name: 'filelist', params: { view: 'files' } })
})
@ -130,11 +138,7 @@ describe('Navigation', () => {
})
describe('Navigation API', () => {
let Navigation: Navigation
beforeEach(async () => {
delete window._nc_navigation
Navigation = getNavigation()
mockWindow()
await router.replace({ name: 'filelist', params: { view: 'files' } })
@ -144,7 +148,7 @@ describe('Navigation API', () => {
beforeEach(cleanup)
it('Check API entries rendering', async () => {
Navigation.register(createView('files', 'Files'))
navigation.register(createView('files', 'Files'))
const component = render(NavigationView, {
router,
@ -171,8 +175,8 @@ describe('Navigation API', () => {
})
it('Adds a new entry and render', async () => {
Navigation.register(createView('files', 'Files'))
Navigation.register(createView('sharing', 'Sharing'))
navigation.register(createView('files', 'Files'))
navigation.register(createView('sharing', 'Sharing'))
const component = render(NavigationView, {
router,
@ -198,9 +202,9 @@ describe('Navigation API', () => {
})
it('Adds a new children, render and open menu', async () => {
Navigation.register(createView('files', 'Files'))
Navigation.register(createView('sharing', 'Sharing'))
Navigation.register(createView('sharingin', 'Shared with me', 'sharing'))
navigation.register(createView('files', 'Files'))
navigation.register(createView('sharing', 'Sharing'))
navigation.register(createView('sharingin', 'Shared with me', 'sharing'))
const component = render(NavigationView, {
router,
@ -272,7 +276,7 @@ function createView(id: string, name: string, parent?: string) {
return new View({
id,
name,
getContents: async () => ({ folder: {} as Folder, contents: [] }),
getContents: async () => ({ folder: {} as IFolder, contents: [] }),
icon: FolderSvg,
order: 1,
parent,

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Navigation, Folder as NcFolder } from '@nextcloud/files'
import type { IFolder } from '@nextcloud/files'
import * as eventBus from '@nextcloud/event-bus'
import * as filesUtils from '@nextcloud/files'
@ -23,30 +23,26 @@ window.OC = {
TAG_FAVORITE: '_$!<Favorite>!$_',
}
declare global {
interface Window {
_nc_navigation?: Navigation
const navigation = getNavigation()
beforeEach(() => {
vi.resetAllMocks()
const views = [...navigation.views]
for (const view of views) {
navigation.remove(view.id)
}
}
expect(navigation.views).toHaveLength(0)
})
describe('Favorites view definition', () => {
let Navigation
beforeEach(() => {
vi.resetAllMocks()
delete window._nc_navigation
Navigation = getNavigation()
expect(window._nc_navigation).toBeDefined()
})
test('Default empty favorite view', async () => {
vi.spyOn(eventBus, 'subscribe')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as IFolder, contents: [] }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter((view) => view.parent === 'favorites')
const favoritesView = navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = navigation.views.filter((view) => view.parent === 'favorites')
expect(eventBus.subscribe).toHaveBeenCalledTimes(3)
expect(eventBus.subscribe).toHaveBeenNthCalledWith(1, 'files:favorites:added', expect.anything())
@ -54,7 +50,7 @@ describe('Favorites view definition', () => {
expect(eventBus.subscribe).toHaveBeenNthCalledWith(3, 'files:node:renamed', expect.anything())
// one main view and no children
expect(Navigation.views.length).toBe(1)
expect(navigation.views.length).toBe(1)
expect(favoritesView).toBeDefined()
expect(favoriteFoldersViews.length).toBe(0)
@ -95,14 +91,14 @@ describe('Favorites view definition', () => {
}),
]
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve(favoriteFolders))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: favoriteFolders }))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as IFolder, contents: favoriteFolders }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter((view) => view.parent === 'favorites')
const favoritesView = navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = navigation.views.filter((view) => view.parent === 'favorites')
// one main view and 3 children
expect(Navigation.views.length).toBe(5)
expect(navigation.views.length).toBe(5)
expect(favoritesView).toBeDefined()
expect(favoriteFoldersViews.length).toBe(4)
@ -129,25 +125,17 @@ describe('Favorites view definition', () => {
})
describe('Dynamic update of favorite folders', () => {
let Navigation
beforeEach(() => {
vi.restoreAllMocks()
delete window._nc_navigation
Navigation = getNavigation()
})
test('Add a favorite folder creates a new entry in the navigation', async () => {
vi.spyOn(eventBus, 'emit')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as IFolder, contents: [] }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter((view) => view.parent === 'favorites')
const favoritesView = navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = navigation.views.filter((view) => view.parent === 'favorites')
// one main view and no children
expect(Navigation.views.length).toBe(1)
expect(navigation.views.length).toBe(1)
expect(favoritesView).toBeDefined()
expect(favoriteFoldersViews.length).toBe(0)
@ -162,8 +150,8 @@ describe('Dynamic update of favorite folders', () => {
// Exec the action
await action.exec({
nodes: [folder],
view: favoritesView,
folder: {} as NcFolder,
view: favoritesView!,
folder: {} as IFolder,
contents: [],
})
@ -181,14 +169,14 @@ describe('Dynamic update of favorite folders', () => {
})]
vi.spyOn(eventBus, 'emit')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve(favoriteFolders))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: favoriteFolders }))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as IFolder, contents: favoriteFolders }))
await registerFavoritesView()
let favoritesView = Navigation.views.find((view) => view.id === 'favorites')
let favoriteFoldersViews = Navigation.views.filter((view) => view.parent === 'favorites')
let favoritesView = navigation.views.find((view) => view.id === 'favorites')
let favoriteFoldersViews = navigation.views.filter((view) => view.parent === 'favorites')
// one main view and no children
expect(Navigation.views.length).toBe(2)
expect(navigation.views.length).toBe(2)
expect(favoritesView).toBeDefined()
expect(favoriteFoldersViews.length).toBe(1)
@ -209,8 +197,8 @@ describe('Dynamic update of favorite folders', () => {
// Exec the action
await action.exec({
nodes: [folder],
view: favoritesView,
folder: {} as NcFolder,
view: favoritesView!,
folder: {} as IFolder,
contents: [],
})
@ -219,11 +207,11 @@ describe('Dynamic update of favorite folders', () => {
expect(eventBus.emit).toHaveBeenCalledWith('files:node:updated', folder)
expect(fo).toHaveBeenCalled()
favoritesView = Navigation.views.find((view) => view.id === 'favorites')
favoriteFoldersViews = Navigation.views.filter((view) => view.parent === 'favorites')
favoritesView = navigation.views.find((view) => view.id === 'favorites')
favoriteFoldersViews = navigation.views.filter((view) => view.parent === 'favorites')
// one main view and no children
expect(Navigation.views.length).toBe(1)
expect(navigation.views.length).toBe(1)
expect(favoritesView).toBeDefined()
expect(favoriteFoldersViews.length).toBe(0)
})
@ -231,14 +219,14 @@ describe('Dynamic update of favorite folders', () => {
test('Renaming a favorite folder updates the navigation', async () => {
vi.spyOn(eventBus, 'emit')
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as IFolder, contents: [] }))
await registerFavoritesView()
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter((view) => view.parent === 'favorites')
const favoritesView = navigation.views.find((view) => view.id === 'favorites')
const favoriteFoldersViews = navigation.views.filter((view) => view.parent === 'favorites')
// one main view and no children
expect(Navigation.views.length).toBe(1)
expect(navigation.views.length).toBe(1)
expect(favoritesView).toBeDefined()
expect(favoriteFoldersViews.length).toBe(0)
@ -255,8 +243,8 @@ describe('Dynamic update of favorite folders', () => {
// Exec the action
await action.exec({
nodes: [folder],
view: favoritesView,
folder: {} as NcFolder,
view: favoritesView!,
folder: {} as IFolder,
contents: [],
})
expect(eventBus.emit).toHaveBeenCalledWith('files:favorites:added', folder)

View file

@ -1,14 +1,15 @@
import type { Folder } from '@nextcloud/files'
/**
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFolder } from '@nextcloud/files'
import type { ComponentPublicInstance, VueConstructor } from 'vue'
import { Header, registerFileListHeaders } from '@nextcloud/files'
import { registerFileListHeader } from '@nextcloud/files'
import Vue from 'vue'
type IFilesHeaderNoteToRecipient = ComponentPublicInstance & { updateFolder: (folder: Folder) => void }
type IFilesHeaderNoteToRecipient = ComponentPublicInstance & { updateFolder: (folder: IFolder) => void }
/**
* Register the "note to recipient" as a files list header
@ -17,19 +18,19 @@ export default function registerNoteToRecipient() {
let FilesHeaderNoteToRecipient: VueConstructor
let instance: IFilesHeaderNoteToRecipient
registerFileListHeaders(new Header({
registerFileListHeader({
id: 'note-to-recipient',
order: 0,
// Always if there is a note
enabled: (folder: Folder) => Boolean(folder.attributes.note),
enabled: (folder: IFolder) => Boolean(folder.attributes.note),
// Update the root folder if needed
updated: (folder: Folder) => {
updated: (folder: IFolder) => {
if (instance) {
instance.updateFolder(folder)
}
},
// render simply spawns the component
render: async (el: HTMLElement, folder: Folder) => {
render: async (el: HTMLElement, folder: IFolder) => {
if (FilesHeaderNoteToRecipient === undefined) {
const { default: component } = await import('../views/FilesHeaderNoteToRecipient.vue')
FilesHeaderNoteToRecipient = Vue.extend(component)
@ -37,5 +38,5 @@ export default function registerNoteToRecipient() {
instance = new FilesHeaderNoteToRecipient().$mount(el) as unknown as IFilesHeaderNoteToRecipient
instance.updateFolder(folder)
},
}))
})
}

View file

@ -3,44 +3,40 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Navigation, View } from '@nextcloud/files'
import type { View } from '@nextcloud/files'
import type { OCSResponse } from '@nextcloud/typings/ocs'
import axios from '@nextcloud/axios'
import { Folder, getNavigation } from '@nextcloud/files'
import { getNavigation } from '@nextcloud/files'
import * as ncInitialState from '@nextcloud/initial-state'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import registerSharingViews from './shares.ts'
import '../main.ts'
declare global {
interface Window {
_nc_navigation?: Navigation
const navigation = getNavigation()
beforeEach(() => {
vi.resetAllMocks()
const views = [...navigation.views]
for (const view of views) {
navigation.remove(view.id)
}
}
expect(navigation.views).toHaveLength(0)
})
describe('Sharing views definition', () => {
let Navigation
beforeEach(() => {
delete window._nc_navigation
Navigation = getNavigation()
expect(window._nc_navigation).toBeDefined()
})
test('Default values', () => {
vi.spyOn(Navigation, 'register')
expect(Navigation.views.length).toBe(0)
vi.spyOn(navigation, 'register')
registerSharingViews()
const shareOverviewView = Navigation.views.find((view) => view.id === 'shareoverview') as View
const sharesChildViews = Navigation.views.filter((view) => view.parent === 'shareoverview') as View[]
const shareOverviewView = navigation.views.find((view) => view.id === 'shareoverview') as View
const sharesChildViews = navigation.views.filter((view) => view.parent === 'shareoverview') as View[]
expect(Navigation.register).toHaveBeenCalledTimes(7)
expect(navigation.register).toHaveBeenCalledTimes(7)
// one main view and no children
expect(Navigation.views.length).toBe(7)
expect(navigation.views.length).toBe(7)
expect(shareOverviewView).toBeDefined()
expect(sharesChildViews.length).toBe(6)
@ -76,35 +72,28 @@ describe('Sharing views definition', () => {
})
test('Shared with others view is not registered if user has no storage quota', () => {
vi.spyOn(Navigation, 'register')
vi.spyOn(navigation, 'register')
const spy = vi.spyOn(ncInitialState, 'loadState').mockImplementationOnce(() => ({ quota: 0 }))
expect(Navigation.views.length).toBe(0)
expect(navigation.views.length).toBe(0)
registerSharingViews()
expect(Navigation.register).toHaveBeenCalledTimes(6)
expect(Navigation.views.length).toBe(6)
expect(navigation.register).toHaveBeenCalledTimes(6)
expect(navigation.views.length).toBe(6)
const shareOverviewView = Navigation.views.find((view) => view.id === 'shareoverview') as View
const sharesChildViews = Navigation.views.filter((view) => view.parent === 'shareoverview') as View[]
const shareOverviewView = navigation.views.find((view) => view.id === 'shareoverview') as View
const sharesChildViews = navigation.views.filter((view) => view.parent === 'shareoverview') as View[]
expect(shareOverviewView).toBeDefined()
expect(sharesChildViews.length).toBe(5)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith('files', 'storageStats', { quota: -1 })
const sharedWithOthersView = Navigation.views.find((view) => view.id === 'sharingout')
const sharedWithOthersView = navigation.views.find((view) => view.id === 'sharingout')
expect(sharedWithOthersView).toBeUndefined()
})
})
describe('Sharing views contents', () => {
let Navigation
beforeEach(() => {
delete window._nc_navigation
Navigation = getNavigation()
expect(window._nc_navigation).toBeDefined()
})
test('Sharing overview get contents', async () => {
vi.spyOn(axios, 'get').mockImplementation(async (): Promise<any> => {
return {
@ -122,11 +111,11 @@ describe('Sharing views contents', () => {
})
registerSharingViews()
expect(Navigation.views.length).toBe(7)
Navigation.views.forEach(async (view: View) => {
const content = await view.getContents('/')
expect(navigation.views.length).toBe(7)
for (const view of navigation.views) {
const content = await view.getContents('/', { signal: new AbortController().signal })
expect(content.contents).toStrictEqual([])
expect(content.folder).toBeInstanceOf(Folder)
})
expect(content.folder).toBeTypeOf('object')
}
})
})

View file

@ -170,13 +170,13 @@ describe('files_trashbin: file actions - restore action', () => {
})
it('does not delete node from view if request failed', async () => {
const node = new Folder({ owner: 'test', source: 'https://example.com/remote.php/dav/trashbin/test/folder', root: '/trashbin/test/', permissions: PERMISSION_ALL })
vi.spyOn(window.console, 'error').mockImplementation(() => {})
const emitSpy = vi.spyOn(ncEventBus, 'emit')
axiosMock.request.mockImplementationOnce(() => {
throw new Error()
})
const emitSpy = vi.spyOn(ncEventBus, 'emit')
const node = new Folder({ owner: 'test', source: 'https://example.com/remote.php/dav/trashbin/test/folder', root: '/trashbin/test/', permissions: PERMISSION_ALL })
expect(await restoreAction.exec({
nodes: [node],
view: trashbinView,
@ -189,6 +189,7 @@ describe('files_trashbin: file actions - restore action', () => {
})
it('batch: only returns success if all requests worked', async () => {
vi.spyOn(window.console, 'error').mockImplementation(() => {})
const node = new Folder({ owner: 'test', source: 'https://example.com/remote.php/dav/trashbin/test/folder', root: '/trashbin/test/', permissions: PERMISSION_ALL })
expect(await restoreAction.execBatch!({
@ -201,6 +202,7 @@ describe('files_trashbin: file actions - restore action', () => {
})
it('batch: only returns success if all requests worked - one failed', async () => {
vi.spyOn(window.console, 'error').mockImplementation(() => {})
const node = new Folder({ owner: 'test', source: 'https://example.com/remote.php/dav/trashbin/test/folder', root: '/trashbin/test/', permissions: PERMISSION_ALL })
axiosMock.request.mockImplementationOnce(() => {

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@
"@nextcloud/capabilities": "^1.2.1",
"@nextcloud/dialogs": "^7.2.0",
"@nextcloud/event-bus": "^3.3.3",
"@nextcloud/files": "^4.0.0-rc.1",
"@nextcloud/files": "^4.0.0-rc.3",
"@nextcloud/initial-state": "^3.0.0",
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/logger": "^3.0.3",

View file

@ -4,7 +4,6 @@
*/
import type { User } from '@nextcloud/e2e-test-server/cypress'
import type { IFileAction } from '@nextcloud/files'
import { getActionButtonForFileId, getActionEntryForFileId, getRowForFile, getSelectionActionButton, getSelectionActionEntry, selectRowForFile } from './FilesUtils.ts'
@ -12,12 +11,6 @@ const ACTION_DELETE = 'delete'
const ACTION_COPY_MOVE = 'move-copy'
const ACTION_DETAILS = 'details'
declare global {
interface Window {
_nc_fileactions: IFileAction[]
}
}
// Those two arrays doesn't represent the full list of actions
// the goal is to test a few, we're not trying to match the full feature set
const expectedDefaultActionsIDs = [
@ -57,77 +50,6 @@ describe('Files: Actions', { testIsolation: true }, () => {
})
})
it('Show some nested actions', () => {
const parent: IFileAction = {
id: 'nested-action',
displayName: () => 'Nested Action',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
}
const child1: IFileAction = {
id: 'nested-child-1',
displayName: () => 'Nested Child 1',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
}
const child2: IFileAction = {
id: 'nested-child-2',
displayName: () => 'Nested Child 2',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
}
cy.visit('/apps/files', {
// Cannot use registerFileAction here
onBeforeLoad: (win) => {
if (!win._nc_fileactions) {
win._nc_fileactions = []
}
// Cannot use registerFileAction here
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
},
})
// Open the menu
getActionButtonForFileId(fileId)
.scrollIntoView()
.click({ force: true })
// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
getActionEntryForFileId(fileId, 'menu-back').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-1').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-2').should('not.exist')
// Click on the parent action
getActionEntryForFileId(fileId, 'nested-action')
.should('be.visible')
.click()
// Check we have the children and the back button but not the parent
getActionEntryForFileId(fileId, 'nested-action').should('not.exist')
getActionEntryForFileId(fileId, 'menu-back').should('be.visible')
getActionEntryForFileId(fileId, 'nested-child-1').should('be.visible')
getActionEntryForFileId(fileId, 'nested-child-2').should('be.visible')
// Click on the back button
getActionEntryForFileId(fileId, 'menu-back')
.should('be.visible')
.click()
// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
getActionEntryForFileId(fileId, 'menu-back').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-1').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-2').should('not.exist')
})
it('Show some actions for a selection', () => {
cy.visit('/apps/files')
getRowForFile('image.jpg').should('be.visible')
@ -145,126 +67,4 @@ describe('Files: Actions', { testIsolation: true }, () => {
getSelectionActionEntry(actionId).should('be.visible')
})
})
it('Show some nested actions for a selection', () => {
const parent: IFileAction = {
id: 'nested-action',
displayName: () => 'Nested Action',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
}
const child1: IFileAction = {
id: 'nested-child-1',
displayName: () => 'Nested Child 1',
exec: cy.spy(),
execBatch: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
}
const child2: IFileAction = {
id: 'nested-child-2',
displayName: () => 'Nested Child 2',
exec: cy.spy(),
execBatch: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
}
cy.visit('/apps/files', {
// Cannot use registerFileAction here
onBeforeLoad: (win) => {
if (!win._nc_fileactions) {
win._nc_fileactions = []
}
// Cannot use registerFileAction here
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
},
})
selectRowForFile('image.jpg')
// Open the menu
getSelectionActionButton().click({ force: true })
// Check we have the parent action but not the children
getSelectionActionEntry('nested-action').should('be.visible')
getSelectionActionEntry('menu-back').should('not.exist')
getSelectionActionEntry('nested-child-1').should('not.exist')
getSelectionActionEntry('nested-child-2').should('not.exist')
// Click on the parent action
getSelectionActionEntry('nested-action')
.find('button').last()
.should('exist').click({ force: true })
// Check we have the children and the back button but not the parent
getSelectionActionEntry('nested-action').should('not.exist')
getSelectionActionEntry('menu-back').should('be.visible')
getSelectionActionEntry('nested-child-1').should('be.visible')
getSelectionActionEntry('nested-child-2').should('be.visible')
// Click on the back button
getSelectionActionEntry('menu-back')
.find('button').last()
.should('exist').click({ force: true })
// Check we have the parent action but not the children
getSelectionActionEntry('nested-action').should('be.visible')
getSelectionActionEntry('menu-back').should('not.exist')
getSelectionActionEntry('nested-child-1').should('not.exist')
getSelectionActionEntry('nested-child-2').should('not.exist')
})
it('Do not show parent if nested action has no batch support', () => {
const parent: IFileAction = {
id: 'nested-action',
displayName: () => 'Nested Action',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
}
const child1: IFileAction = {
id: 'nested-child-1',
displayName: () => 'Nested Child 1',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
}
const child2: IFileAction = {
id: 'nested-child-2',
displayName: () => 'Nested Child 2',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
}
cy.visit('/apps/files', {
// Cannot use registerFileAction here
onBeforeLoad: (win) => {
if (!win._nc_fileactions) {
win._nc_fileactions = []
}
// Cannot use registerFileAction here
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
},
})
selectRowForFile('image.jpg')
// Open the menu
getSelectionActionButton().click({ force: true })
// Check we have the parent action but not the children
getSelectionActionEntry('nested-action').should('not.exist')
getSelectionActionEntry('menu-back').should('not.exist')
getSelectionActionEntry('nested-child-1').should('not.exist')
getSelectionActionEntry('nested-child-2').should('not.exist')
})
})

View file

@ -56,7 +56,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0
@ -157,6 +157,9 @@ This file is generated from multiple sources. Included packages:
- vue-loader
- version: 15.11.1
- license: MIT
- vue-router
- version: 3.6.5
- license: MIT
- vue
- version: 2.7.16
- license: MIT

View file

@ -91,7 +91,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

View file

@ -84,7 +84,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

4
dist/8577-8577.js vendored

File diff suppressed because one or more lines are too long

View file

@ -108,7 +108,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

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

View file

@ -1,4 +1,4 @@
import{c as u}from"./index-D9L8KHF3.chunk.mjs";import{g as y,e as c}from"./index-6_gsQFyp.chunk.mjs";import{t as o}from"./translation-DoG5ZELJ-2ffMJaM4.chunk.mjs";import{c as f}from"./index-Buqk-yLP.chunk.mjs";import{a as h}from"./createElementId-DhjFt1I9-Bjk2333q.chunk.mjs";import{g as b}from"./dav-DrcZNsCL.chunk.mjs";const r=y().setApp("systemtags").detectUser().build(),V={userVisible:!0,userAssignable:!0,canAssign:!0},x=Object.freeze({"display-name":"displayName","user-visible":"userVisible","user-assignable":"userAssignable","can-assign":"canAssign"});function p(t){return t.map(({props:s})=>Object.fromEntries(Object.entries(s).map(([e,a])=>(e=x[e]??e,a=e==="displayName"?String(a):a,[e,a]))))}function w(t){const s=t.indexOf("?");s>0&&(t=t.substring(0,s));const e=t.split("/");let a;do a=e[e.length-1],e.pop();while(!a&&e.length>0);return Number(a)}function v(t){if("name"in t&&!("displayName"in t))return{...t};const s={...t};return s.name=s.displayName,delete s.displayName,s}function N(t){const s=t.attributes?.["system-tags"]?.["system-tag"];return s===void 0?[]:[s].flat().map(e=>typeof e=="string"?e:e.text)}function P(t,s){t.attributes["system-tags"]={"system-tag":s},c("files:node:updated",t)}const n=b(),l=`<?xml version="1.0"?>
import{c as u}from"./index-D9L8KHF3.chunk.mjs";import{g as y,e as c}from"./index-6_gsQFyp.chunk.mjs";import{t as o}from"./translation-DoG5ZELJ-2ffMJaM4.chunk.mjs";import{c as f}from"./index-Buqk-yLP.chunk.mjs";import{a as h}from"./createElementId-DhjFt1I9-Bjk2333q.chunk.mjs";import{g as b}from"./dav-BYbKV7ND.chunk.mjs";const r=y().setApp("systemtags").detectUser().build(),V={userVisible:!0,userAssignable:!0,canAssign:!0},x=Object.freeze({"display-name":"displayName","user-visible":"userVisible","user-assignable":"userAssignable","can-assign":"canAssign"});function p(t){return t.map(({props:s})=>Object.fromEntries(Object.entries(s).map(([e,a])=>(e=x[e]??e,a=e==="displayName"?String(a):a,[e,a]))))}function w(t){const s=t.indexOf("?");s>0&&(t=t.substring(0,s));const e=t.split("/");let a;do a=e[e.length-1],e.pop();while(!a&&e.length>0);return Number(a)}function v(t){if("name"in t&&!("displayName"in t))return{...t};const s={...t};return s.name=s.displayName,delete s.displayName,s}function N(t){const s=t.attributes?.["system-tags"]?.["system-tag"];return s===void 0?[]:[s].flat().map(e=>typeof e=="string"?e:e.text)}function P(t,s){t.attributes["system-tags"]={"system-tag":s},c("files:node:updated",t)}const n=b(),l=`<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
<d:prop>
<oc:id />
@ -40,4 +40,4 @@ import{c as u}from"./index-D9L8KHF3.chunk.mjs";import{g as y,e as c}from"./index
</d:prop>
</d:remove>
</d:propertyupdate>`),await n.customRequest(i,{method:"PROPPATCH",data:d,headers:{"if-match":a}})}async function H(t){const s=t?"1":"0",e=h("/apps/provisioning_api/api/v1/config/apps/{appId}/{key}",{appId:"systemtags",key:"restrict_creation_to_admin"});await f();const{data:a}=await u.post(e,{value:s});return a}export{_ as a,H as b,R as c,V as d,N as e,D as f,q as g,P as h,C as i,r as l,k as s,T as u};
//# sourceMappingURL=api-DiMGachH.chunk.mjs.map
//# sourceMappingURL=api-DjnjCM4j.chunk.mjs.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -73,7 +73,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

View file

@ -154,7 +154,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

4
dist/core-login.js vendored

File diff suppressed because one or more lines are too long

View file

@ -96,7 +96,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

4
dist/core-main.js vendored

File diff suppressed because one or more lines are too long

View file

@ -132,7 +132,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

2
dist/dav-BYbKV7ND.chunk.mjs vendored Normal file
View file

@ -0,0 +1,2 @@
import{a as h,b as N,o as y}from"./index-6_gsQFyp.chunk.mjs";import{d as E}from"./createElementId-DhjFt1I9-Bjk2333q.chunk.mjs";import{i as l,b}from"./index-xFugdZPW.chunk.mjs";import{l as D,_ as $}from"./index-Dpy2yt9b.chunk.mjs";import{s as n,l as u,N as P,a as T,b as R,P as i}from"./folder-D9CBQngR-Cw5TS1GK.chunk.mjs";function x(e=""){let s=i.NONE;return e&&(e.includes("G")&&(s|=i.READ),e.includes("W")&&(s|=i.WRITE),e.includes("CK")&&(s|=i.CREATE),e.includes("NV")&&(s|=i.UPDATE),e.includes("D")&&(s|=i.DELETE),e.includes("R")&&(s|=i.SHARE)),s}const v=["d:getcontentlength","d:getcontenttype","d:getetag","d:getlastmodified","d:creationdate","d:displayname","d:quota-available-bytes","d:resourcetype","nc:has-preview","nc:is-encrypted","nc:mount-type","oc:comments-unread","oc:favorite","oc:fileid","oc:owner-display-name","oc:owner-id","oc:permissions","oc:size"],g={d:"DAV:",nc:"http://nextcloud.org/ns",oc:"http://owncloud.org/ns",ocs:"http://open-collaboration-services.org/ns"};function S(e,s={nc:"http://nextcloud.org/ns"}){n.davNamespaces??={...g},n.davProperties??=[...v];const a={...n.davNamespaces,...s};if(n.davProperties.find(t=>t===e))return u.warn(`${e} already registered`,{prop:e}),!1;if(e.startsWith("<")||e.split(":").length!==2)return u.error(`${e} is not valid. See example: 'oc:fileid'`,{prop:e}),!1;const o=e.split(":")[0];return a[o]?(n.davProperties.push(e),n.davNamespaces=a,!0):(u.error(`${e} namespace unknown`,{prop:e,namespaces:a}),!1)}function z(){return n.davProperties??=[...v],n.davProperties.map(e=>`<${e} />`).join(" ")}function C(){return n.davNamespaces??={...g},Object.keys(n.davNamespaces).map(e=>`xmlns:${e}="${n.davNamespaces?.[e]}"`).join(" ")}function A(){return l()?`/files/${b()}`:`/files/${h()?.uid}`}const q=A();function W(){const e=E("dav");return l()?e.replace("remote.php","public.php"):e}const w=W();function O(e=w,s={}){const a=D(e,{headers:s});function o(t){a.setHeaders({...s,"X-Requested-With":"XMLHttpRequest",requesttoken:t??""})}return y(o),o(N()),$().patch("fetch",(t,r)=>{const c=r.headers;return c?.method&&(r.method=c.method,delete c.method),fetch(t,r)}),a}function U(e,s=q,a=w){let o=h()?.uid;if(l())o=o??"anonymous";else if(!o)throw new Error("No user id found");const t=e.props,r=x(t?.permissions),c=String(t?.["owner-id"]||o),f=t.fileid||0,d=new Date(Date.parse(e.lastmod)),p=new Date(Date.parse(t.creationdate)),m={id:f,source:`${a}${e.filename}`,mtime:!isNaN(d.getTime())&&d.getTime()!==0?d:void 0,crtime:!isNaN(p.getTime())&&p.getTime()!==0?p:void 0,mime:e.mime||"application/octet-stream",displayname:t.displayname!==void 0?String(t.displayname):void 0,size:t?.size||Number.parseInt(t.getcontentlength||"0"),status:f<0?P.FAILED:void 0,permissions:r,owner:c,root:s,attributes:{...e,...t,hasPreview:t?.["has-preview"]}};return delete m.attributes?.props,e.type==="file"?new T(m):new R(m)}export{C as a,z as b,S as c,w as d,W as e,A as f,O as g,U as r};
//# sourceMappingURL=dav-BYbKV7ND.chunk.mjs.map

View file

@ -3,5 +3,5 @@ SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
This file is generated from multiple sources. Included packages:
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later

1
dist/dav-BYbKV7ND.chunk.mjs.map vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -3,5 +3,5 @@ SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
This file is generated from multiple sources. Included packages:
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later

View file

@ -1,2 +0,0 @@
import{a as w,b as h,o as g}from"./index-6_gsQFyp.chunk.mjs";import{d as y}from"./createElementId-DhjFt1I9-Bjk2333q.chunk.mjs";import{i as u,b}from"./index-xFugdZPW.chunk.mjs";import{l as E,_ as D}from"./index-Dpy2yt9b.chunk.mjs";import{l as m,N,a as $,b as T,P as i}from"./folder-CeyZUHai-CVGj8rKf.chunk.mjs";function R(e=""){let t=i.NONE;return e&&(e.includes("G")&&(t|=i.READ),e.includes("W")&&(t|=i.WRITE),e.includes("CK")&&(t|=i.CREATE),e.includes("NV")&&(t|=i.UPDATE),e.includes("D")&&(t|=i.DELETE),e.includes("R")&&(t|=i.SHARE)),t}const _=["d:getcontentlength","d:getcontenttype","d:getetag","d:getlastmodified","d:creationdate","d:displayname","d:quota-available-bytes","d:resourcetype","nc:has-preview","nc:is-encrypted","nc:mount-type","oc:comments-unread","oc:favorite","oc:fileid","oc:owner-display-name","oc:owner-id","oc:permissions","oc:size"],f={d:"DAV:",nc:"http://nextcloud.org/ns",oc:"http://owncloud.org/ns",ocs:"http://open-collaboration-services.org/ns"};function S(e,t={nc:"http://nextcloud.org/ns"}){typeof window._nc_dav_properties>"u"&&(window._nc_dav_properties=[..._],window._nc_dav_namespaces={...f});const s={...window._nc_dav_namespaces,...t};if(window._nc_dav_properties.find(n=>n===e))return m.warn(`${e} already registered`,{prop:e}),!1;if(e.startsWith("<")||e.split(":").length!==2)return m.error(`${e} is not valid. See example: 'oc:fileid'`,{prop:e}),!1;const o=e.split(":")[0];return s[o]?(window._nc_dav_properties.push(e),window._nc_dav_namespaces=s,!0):(m.error(`${e} namespace unknown`,{prop:e,namespaces:s}),!1)}function W(){return typeof window._nc_dav_properties>"u"&&(window._nc_dav_properties=[..._]),window._nc_dav_properties.map(e=>`<${e} />`).join(" ")}function j(){return typeof window._nc_dav_namespaces>"u"&&(window._nc_dav_namespaces={...f}),Object.keys(window._nc_dav_namespaces).map(e=>`xmlns:${e}="${window._nc_dav_namespaces?.[e]}"`).join(" ")}function x(){return u()?`/files/${b()}`:`/files/${w()?.uid}`}const A=x();function q(){const e=y("dav");return u()?e.replace("remote.php","public.php"):e}const v=q();function z(e=v,t={}){const s=E(e,{headers:t});function o(n){s.setHeaders({...t,"X-Requested-With":"XMLHttpRequest",requesttoken:n??""})}return g(o),o(h()),D().patch("fetch",(n,a)=>{const r=a.headers;return r?.method&&(a.method=r.method,delete r.method),fetch(n,a)}),s}function C(e,t=A,s=v){let o=w()?.uid;if(u())o=o??"anonymous";else if(!o)throw new Error("No user id found");const n=e.props,a=R(n?.permissions),r=String(n?.["owner-id"]||o),l=n.fileid||0,d=new Date(Date.parse(e.lastmod)),c=new Date(Date.parse(n.creationdate)),p={id:l,source:`${s}${e.filename}`,mtime:!isNaN(d.getTime())&&d.getTime()!==0?d:void 0,crtime:!isNaN(c.getTime())&&c.getTime()!==0?c:void 0,mime:e.mime||"application/octet-stream",displayname:n.displayname!==void 0?String(n.displayname):void 0,size:n?.size||Number.parseInt(n.getcontentlength||"0"),status:l<0?N.FAILED:void 0,permissions:a,owner:r,root:t,attributes:{...e,...n,hasPreview:n?.["has-preview"]}};return delete p.attributes?.props,e.type==="file"?new $(p):new T(p)}export{j as a,W as b,S as c,v as d,q as e,x as f,z as g,C as r};
//# sourceMappingURL=dav-DrcZNsCL.chunk.mjs.map

File diff suppressed because one or more lines are too long

4
dist/files-init.js vendored

File diff suppressed because one or more lines are too long

View file

@ -136,7 +136,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

View file

@ -140,7 +140,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -46,7 +46,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0
@ -123,6 +123,9 @@ This file is generated from multiple sources. Included packages:
- vue-demi
- version: 0.14.10
- license: MIT
- vue-router
- version: 3.6.5
- license: MIT
- vue
- version: 2.7.16
- license: MIT

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

View file

@ -42,7 +42,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -53,7 +53,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -94,7 +94,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
import{C as c,V as _,g as v,a as x,b as V}from"./index-BD5nfMuU.chunk.mjs";import{a as o,g as H,e as p}from"./index-6_gsQFyp.chunk.mjs";import{c as u,i as A}from"./index-D9L8KHF3.chunk.mjs";import{a as b,d as C,g as N}from"./index-JpgrUA2Z-DPCs44Lo.chunk.mjs";import{P as E}from"./folder-CeyZUHai-CVGj8rKf.chunk.mjs";import{d as S,e as $,l as I}from"./index-xFugdZPW.chunk.mjs";import{t as n,g as y,a as f}from"./translation-DoG5ZELJ-2ffMJaM4.chunk.mjs";import{g as D,d as L}from"./createElementId-DhjFt1I9-Bjk2333q.chunk.mjs";import{g as M,a as T,b as k,r as F,d as B}from"./dav-DrcZNsCL.chunk.mjs";import{f as U}from"./index-Dzo4H_NA.chunk.mjs";import{h as P}from"./runtime-dom.esm-bundler-DSTOTAEf.chunk.mjs";import{N as O}from"./NcUserBubble-DPAmU2_J-B8QraJ_f.chunk.mjs";import"./string_decoder-BO00msnV.chunk.mjs";import"./NcNoteCard-CVhtNL04-CdF6Qoal.chunk.mjs";import"./logger-D3RVzcfQ-iUjwSNGe.chunk.mjs";import"./index-Dpy2yt9b.chunk.mjs";import"./mdi-kAZc0JKn.chunk.mjs";import"./NcAvatar-DmUGApWA-JvdYd-8p.chunk.mjs";import"./colors-Go3zmZRD-Bml2X1sg.chunk.mjs";import"./NcUserStatusIcon-CGEf7fej-CR1VhaiT.chunk.mjs";import"./PencilOutline-BPy7Lagu.chunk.mjs";import"./NcDateTime.vue_vue_type_script_setup_true_lang-BhB8yA4U-Bb9gAlar.chunk.mjs";const R='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-history" viewBox="0 0 24 24"><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>',q='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-trash-can-outline" viewBox="0 0 24 24"><path d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" /></svg>',g=`/trashbin/${o()?.uid}/trash`,z=M(),K=`<?xml version="1.0"?>
import{C as c,V as _,g as v,a as x,b as V}from"./index-DqvvZoL1.chunk.mjs";import{a as o,g as H,e as p}from"./index-6_gsQFyp.chunk.mjs";import{c as u,i as A}from"./index-D9L8KHF3.chunk.mjs";import{a as b,d as C,g as N}from"./index-JpgrUA2Z-DPCs44Lo.chunk.mjs";import{P as E}from"./folder-D9CBQngR-Cw5TS1GK.chunk.mjs";import{d as S,e as $,l as I}from"./index-xFugdZPW.chunk.mjs";import{t as n,g as y,a as f}from"./translation-DoG5ZELJ-2ffMJaM4.chunk.mjs";import{g as D,d as L}from"./createElementId-DhjFt1I9-Bjk2333q.chunk.mjs";import{g as M,a as T,b as k,r as F,d as B}from"./dav-BYbKV7ND.chunk.mjs";import{f as U}from"./index-Dzo4H_NA.chunk.mjs";import{h as P}from"./runtime-dom.esm-bundler-DSTOTAEf.chunk.mjs";import{N as O}from"./NcUserBubble-DPAmU2_J-B8QraJ_f.chunk.mjs";import"./string_decoder-BO00msnV.chunk.mjs";import"./NcNoteCard-CVhtNL04-CdF6Qoal.chunk.mjs";import"./logger-D3RVzcfQ-iUjwSNGe.chunk.mjs";import"./index-Dpy2yt9b.chunk.mjs";import"./mdi-kAZc0JKn.chunk.mjs";import"./NcAvatar-DmUGApWA-JvdYd-8p.chunk.mjs";import"./colors-Go3zmZRD-Bml2X1sg.chunk.mjs";import"./NcUserStatusIcon-CGEf7fej-CR1VhaiT.chunk.mjs";import"./PencilOutline-BPy7Lagu.chunk.mjs";import"./NcDateTime.vue_vue_type_script_setup_true_lang-BhB8yA4U-Bb9gAlar.chunk.mjs";const R='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-history" viewBox="0 0 24 24"><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>',q='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-trash-can-outline" viewBox="0 0 24 24"><path d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z" /></svg>',g=`/trashbin/${o()?.uid}/trash`,z=M(),K=`<?xml version="1.0"?>
<d:propfind ${T()}>
<d:prop>
<nc:trashbin-deletion-time />

View file

@ -1,3 +1,3 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[window.OC.filePath('', '', 'dist/FilesVersionsSidebarTab-BJZtgL6P.chunk.mjs'),window.OC.filePath('', '', 'dist/index-JpgrUA2Z-DPCs44Lo.chunk.mjs'),window.OC.filePath('', '', 'dist/index-xFugdZPW.chunk.mjs'),window.OC.filePath('', '', 'dist/index-Dzo4H_NA.chunk.mjs'),window.OC.filePath('', '', 'dist/runtime-dom.esm-bundler-DSTOTAEf.chunk.mjs'),window.OC.filePath('', '', 'dist/createElementId-DhjFt1I9-Bjk2333q.chunk.mjs'),window.OC.filePath('', '', 'dist/translation-DoG5ZELJ-2ffMJaM4.chunk.mjs'),window.OC.filePath('', '', 'dist/index-6_gsQFyp.chunk.mjs'),window.OC.filePath('', '', 'dist/logger-D3RVzcfQ-iUjwSNGe.chunk.mjs'),window.OC.filePath('', '', 'dist/logger-D3RVzcfQ-Bx-uhVVO.chunk.css'),window.OC.filePath('', '', 'dist/mdi-kAZc0JKn.chunk.mjs'),window.OC.filePath('', '', 'dist/mdi-BYHcrfvW.chunk.css'),window.OC.filePath('', '', 'dist/index-PxDoi4mB.chunk.css'),window.OC.filePath('', '', 'dist/NcNoteCard-CVhtNL04-CdF6Qoal.chunk.mjs'),window.OC.filePath('', '', 'dist/NcNoteCard-CVhtNL04-Jq77EThs.chunk.css'),window.OC.filePath('', '', 'dist/index-BD5nfMuU.chunk.mjs'),window.OC.filePath('', '', 'dist/folder-CeyZUHai-CVGj8rKf.chunk.mjs'),window.OC.filePath('', '', 'dist/string_decoder-BO00msnV.chunk.mjs'),window.OC.filePath('', '', 'dist/PencilOutline-BPy7Lagu.chunk.mjs'),window.OC.filePath('', '', 'dist/PencilOutline-DdQinVMt.chunk.css'),window.OC.filePath('', '', 'dist/NcDateTime.vue_vue_type_script_setup_true_lang-BhB8yA4U-Bb9gAlar.chunk.mjs'),window.OC.filePath('', '', 'dist/NcDateTime-DS-ziNw6.chunk.css'),window.OC.filePath('', '', 'dist/NcAvatar-DmUGApWA-JvdYd-8p.chunk.mjs'),window.OC.filePath('', '', 'dist/index-D9L8KHF3.chunk.mjs'),window.OC.filePath('', '', 'dist/colors-Go3zmZRD-Bml2X1sg.chunk.mjs'),window.OC.filePath('', '', 'dist/NcUserStatusIcon-CGEf7fej-CR1VhaiT.chunk.mjs'),window.OC.filePath('', '', 'dist/NcUserStatusIcon-CGEf7fej-Bq_6hmXG.chunk.css'),window.OC.filePath('', '', 'dist/NcAvatar-DmUGApWA-B-07Svbi.chunk.css'),window.OC.filePath('', '', 'dist/TrayArrowDown-zMGl3y4Q.chunk.mjs'),window.OC.filePath('', '', 'dist/TrayArrowDown-CMlC-wJV.chunk.css'),window.OC.filePath('', '', 'dist/TrashCanOutline-DKx7CxBb.chunk.mjs'),window.OC.filePath('', '', 'dist/NcInputField-Bwsh2aHY-Bf_22pmD.chunk.mjs'),window.OC.filePath('', '', 'dist/NcInputField-Bwsh2aHY-_gyGHRGx.chunk.css'),window.OC.filePath('', '', 'dist/dav-DrcZNsCL.chunk.mjs'),window.OC.filePath('', '', 'dist/index-Dpy2yt9b.chunk.mjs'),window.OC.filePath('', '', 'dist/files_versions-FilesVersionsSidebarTab-Cjl2hr1y.chunk.css')])))=>i.map(i=>d[i]);
import{i as s,_ as e}from"./index-xFugdZPW.chunk.mjs";import{r}from"./index-BD5nfMuU.chunk.mjs";import{t}from"./translation-DoG5ZELJ-2ffMJaM4.chunk.mjs";import{d as m,a as n}from"./runtime-dom.esm-bundler-DSTOTAEf.chunk.mjs";import{F as a}from"./folder-CeyZUHai-CVGj8rKf.chunk.mjs";import"./index-6_gsQFyp.chunk.mjs";import"./string_decoder-BO00msnV.chunk.mjs";const d='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-backup-restore" viewBox="0 0 24 24"><path d="M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z" /></svg>',i="files-versions_sidebar-tab";r({id:"files_versions",tagName:i,order:90,displayName:t("files_versions","Versions"),iconSvgInline:d,enabled({node:o}){return!(s()||o.type!==a.File)},async onInit(){const o=n(()=>e(()=>import("./FilesVersionsSidebarTab-BJZtgL6P.chunk.mjs"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]),import.meta.url));window.customElements.define(i,m(o,{shadowRoot:!1}))}});
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[window.OC.filePath('', '', 'dist/FilesVersionsSidebarTab-riNKwZuY.chunk.mjs'),window.OC.filePath('', '', 'dist/index-JpgrUA2Z-DPCs44Lo.chunk.mjs'),window.OC.filePath('', '', 'dist/index-xFugdZPW.chunk.mjs'),window.OC.filePath('', '', 'dist/index-Dzo4H_NA.chunk.mjs'),window.OC.filePath('', '', 'dist/runtime-dom.esm-bundler-DSTOTAEf.chunk.mjs'),window.OC.filePath('', '', 'dist/createElementId-DhjFt1I9-Bjk2333q.chunk.mjs'),window.OC.filePath('', '', 'dist/translation-DoG5ZELJ-2ffMJaM4.chunk.mjs'),window.OC.filePath('', '', 'dist/index-6_gsQFyp.chunk.mjs'),window.OC.filePath('', '', 'dist/logger-D3RVzcfQ-iUjwSNGe.chunk.mjs'),window.OC.filePath('', '', 'dist/logger-D3RVzcfQ-Bx-uhVVO.chunk.css'),window.OC.filePath('', '', 'dist/mdi-kAZc0JKn.chunk.mjs'),window.OC.filePath('', '', 'dist/mdi-BYHcrfvW.chunk.css'),window.OC.filePath('', '', 'dist/index-PxDoi4mB.chunk.css'),window.OC.filePath('', '', 'dist/NcNoteCard-CVhtNL04-CdF6Qoal.chunk.mjs'),window.OC.filePath('', '', 'dist/NcNoteCard-CVhtNL04-Jq77EThs.chunk.css'),window.OC.filePath('', '', 'dist/index-DqvvZoL1.chunk.mjs'),window.OC.filePath('', '', 'dist/folder-D9CBQngR-Cw5TS1GK.chunk.mjs'),window.OC.filePath('', '', 'dist/string_decoder-BO00msnV.chunk.mjs'),window.OC.filePath('', '', 'dist/PencilOutline-BPy7Lagu.chunk.mjs'),window.OC.filePath('', '', 'dist/PencilOutline-DdQinVMt.chunk.css'),window.OC.filePath('', '', 'dist/NcDateTime.vue_vue_type_script_setup_true_lang-BhB8yA4U-Bb9gAlar.chunk.mjs'),window.OC.filePath('', '', 'dist/NcDateTime-DS-ziNw6.chunk.css'),window.OC.filePath('', '', 'dist/NcAvatar-DmUGApWA-JvdYd-8p.chunk.mjs'),window.OC.filePath('', '', 'dist/index-D9L8KHF3.chunk.mjs'),window.OC.filePath('', '', 'dist/colors-Go3zmZRD-Bml2X1sg.chunk.mjs'),window.OC.filePath('', '', 'dist/NcUserStatusIcon-CGEf7fej-CR1VhaiT.chunk.mjs'),window.OC.filePath('', '', 'dist/NcUserStatusIcon-CGEf7fej-Bq_6hmXG.chunk.css'),window.OC.filePath('', '', 'dist/NcAvatar-DmUGApWA-B-07Svbi.chunk.css'),window.OC.filePath('', '', 'dist/TrayArrowDown-zMGl3y4Q.chunk.mjs'),window.OC.filePath('', '', 'dist/TrayArrowDown-CMlC-wJV.chunk.css'),window.OC.filePath('', '', 'dist/TrashCanOutline-DKx7CxBb.chunk.mjs'),window.OC.filePath('', '', 'dist/NcInputField-Bwsh2aHY-Bf_22pmD.chunk.mjs'),window.OC.filePath('', '', 'dist/NcInputField-Bwsh2aHY-_gyGHRGx.chunk.css'),window.OC.filePath('', '', 'dist/dav-BYbKV7ND.chunk.mjs'),window.OC.filePath('', '', 'dist/index-Dpy2yt9b.chunk.mjs'),window.OC.filePath('', '', 'dist/files_versions-FilesVersionsSidebarTab-Cjl2hr1y.chunk.css')])))=>i.map(i=>d[i]);
import{i as s,_ as e}from"./index-xFugdZPW.chunk.mjs";import{r}from"./index-DqvvZoL1.chunk.mjs";import{t}from"./translation-DoG5ZELJ-2ffMJaM4.chunk.mjs";import{d as m,a as n}from"./runtime-dom.esm-bundler-DSTOTAEf.chunk.mjs";import{F as a}from"./folder-D9CBQngR-Cw5TS1GK.chunk.mjs";import"./index-6_gsQFyp.chunk.mjs";import"./string_decoder-BO00msnV.chunk.mjs";const d='<svg xmlns="http://www.w3.org/2000/svg" id="mdi-backup-restore" viewBox="0 0 24 24"><path d="M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z" /></svg>',i="files-versions_sidebar-tab";r({id:"files_versions",tagName:i,order:90,displayName:t("files_versions","Versions"),iconSvgInline:d,enabled({node:o}){return!(s()||o.type!==a.File)},async onInit(){const o=n(()=>e(()=>import("./FilesVersionsSidebarTab-riNKwZuY.chunk.mjs"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]),import.meta.url));window.customElements.define(i,m(o,{shadowRoot:!1}))}});
//# sourceMappingURL=files_versions-sidebar-tab.mjs.map

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

View file

@ -3,5 +3,5 @@ SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
This file is generated from multiple sources. Included packages:
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later

File diff suppressed because one or more lines are too long

View file

@ -3,5 +3,5 @@ SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
This file is generated from multiple sources. Included packages:
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later

File diff suppressed because one or more lines are too long

19
dist/index-DqvvZoL1.chunk.mjs vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -22,7 +22,7 @@ This file is generated from multiple sources. Included packages:
- version: 0.4.4
- license: MIT
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- available-typed-arrays
- version: 1.0.7

File diff suppressed because one or more lines are too long

View file

@ -22,7 +22,7 @@ This file is generated from multiple sources. Included packages:
- version: 0.4.4
- license: MIT
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- available-typed-arrays
- version: 1.0.7

View file

@ -105,7 +105,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.1
- version: 4.0.0-rc.3
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

Some files were not shown because too many files have changed in this diff Show more