mirror of
https://github.com/nextcloud/server.git
synced 2026-02-18 18:28:50 -05:00
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:
commit
1a5679b176
108 changed files with 920 additions and 901 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
124
apps/files/src/composables/useFileActions.spec.ts
Normal file
124
apps/files/src/composables/useFileActions.spec.ts
Normal 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'])
|
||||
})
|
||||
})
|
||||
42
apps/files/src/composables/useFileActions.ts
Normal file
42
apps/files/src/composables/useFileActions.ts
Normal 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)))
|
||||
}
|
||||
133
apps/files/src/composables/useFileListActions.spec.ts
Normal file
133
apps/files/src/composables/useFileListActions.spec.ts
Normal 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'])
|
||||
})
|
||||
})
|
||||
53
apps/files/src/composables/useFileListActions.ts
Normal file
53
apps/files/src/composables/useFileListActions.ts
Normal 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)! }))
|
||||
})
|
||||
}
|
||||
|
|
@ -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'])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
346
build/frontend-legacy/package-lock.json
generated
346
build/frontend-legacy/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
5
dist/1035-1035.js.license
vendored
5
dist/1035-1035.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/1598-1598.js.license
vendored
2
dist/1598-1598.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/2915-2915.js.license
vendored
2
dist/2915-2915.js.license
vendored
|
|
@ -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
4
dist/8577-8577.js
vendored
File diff suppressed because one or more lines are too long
2
dist/8577-8577.js.license
vendored
2
dist/8577-8577.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/8577-8577.js.map
vendored
2
dist/8577-8577.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -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
4
dist/comments-comments-tab.js
vendored
4
dist/comments-comments-tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-comments-tab.js.license
vendored
2
dist/comments-comments-tab.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/comments-comments-tab.js.map
vendored
2
dist/comments-comments-tab.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/comments-init.js
vendored
4
dist/comments-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-init.js.license
vendored
2
dist/comments-init.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/comments-init.js.map
vendored
2
dist/comments-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.license
vendored
2
dist/core-common.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-login.js
vendored
4
dist/core-login.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-login.js.license
vendored
2
dist/core-login.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/core-login.js.map
vendored
2
dist/core-login.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-main.js
vendored
4
dist/core-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.license
vendored
2
dist/core-main.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/dav-BYbKV7ND.chunk.mjs
vendored
Normal file
2
dist/dav-BYbKV7ND.chunk.mjs
vendored
Normal 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
|
||||
|
|
@ -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
1
dist/dav-BYbKV7ND.chunk.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
2
dist/dav-DrcZNsCL.chunk.mjs
vendored
2
dist/dav-DrcZNsCL.chunk.mjs
vendored
|
|
@ -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
|
||||
1
dist/dav-DrcZNsCL.chunk.mjs.map
vendored
1
dist/dav-DrcZNsCL.chunk.mjs.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-init.js
vendored
4
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.license
vendored
2
dist/files-init.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.license
vendored
2
dist/files-main.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
5
dist/files-sidebar.js.license
vendored
5
dist/files-sidebar.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/files_external-init.mjs
vendored
2
dist/files_external-init.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/files_reminders-init.mjs
vendored
2
dist/files_reminders-init.mjs
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
|
|
|
|||
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-init-public.js
vendored
4
dist/files_sharing-init-public.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-init-public.js.license
vendored
2
dist/files_sharing-init-public.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/files_sharing-init-public.js.map
vendored
2
dist/files_sharing-init-public.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-init.js
vendored
4
dist/files_sharing-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-init.js.license
vendored
2
dist/files_sharing-init.js.license
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/files_sharing-init.js.map
vendored
2
dist/files_sharing-init.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/files_trashbin-init.mjs
vendored
2
dist/files_trashbin-init.mjs
vendored
|
|
@ -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 />
|
||||
|
|
|
|||
4
dist/files_versions-sidebar-tab.mjs
vendored
4
dist/files_versions-sidebar-tab.mjs
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
dist/folder-CeyZUHai-CVGj8rKf.chunk.mjs
vendored
2
dist/folder-CeyZUHai-CVGj8rKf.chunk.mjs
vendored
File diff suppressed because one or more lines are too long
1
dist/folder-CeyZUHai-CVGj8rKf.chunk.mjs.map
vendored
1
dist/folder-CeyZUHai-CVGj8rKf.chunk.mjs.map
vendored
File diff suppressed because one or more lines are too long
2
dist/folder-D9CBQngR-Cw5TS1GK.chunk.mjs
vendored
Normal file
2
dist/folder-D9CBQngR-Cw5TS1GK.chunk.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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/folder-D9CBQngR-Cw5TS1GK.chunk.mjs.map
vendored
Normal file
1
dist/folder-D9CBQngR-Cw5TS1GK.chunk.mjs.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
19
dist/index-BD5nfMuU.chunk.mjs
vendored
19
dist/index-BD5nfMuU.chunk.mjs
vendored
File diff suppressed because one or more lines are too long
19
dist/index-DqvvZoL1.chunk.mjs
vendored
Normal file
19
dist/index-DqvvZoL1.chunk.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
|
@ -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
|
||||
2
dist/settings-apps-view-4529.js.license
vendored
2
dist/settings-apps-view-4529.js.license
vendored
|
|
@ -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
Loading…
Reference in a new issue