diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue index c423b698d40d8..a6611200e9be8 100644 --- a/apps/files/src/components/BreadCrumbs.vue +++ b/apps/files/src/components/BreadCrumbs.vue @@ -297,6 +297,7 @@ export default defineComponent({ margin-block: 0; margin-inline: 10px; min-width: 0; + gap: var(--default-grid-baseline); :deep() { a { diff --git a/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts b/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts new file mode 100644 index 0000000000000..1d195d7e049bd --- /dev/null +++ b/apps/files_trashbin/src/fileListActions/emptyTrashAction.ts @@ -0,0 +1,70 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import PQueue from 'p-queue' +import { FileListAction } from '@nextcloud/files' +import { DialogSeverity, getDialogBuilder, showSuccess } from '@nextcloud/dialogs' + +import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw' + +import { deleteNode } from '../../../files/src/actions/deleteUtils.ts' +import { logger } from '../logger.ts' + +const queue = new PQueue({ concurrency: 5 }) + +export const emptyTrashAction = new FileListAction({ + id: 'empty-trash', + + displayName: () => t('files_trashbin', 'Empty deleted files'), + iconSvgInline: () => TrashCanSvg, + order: 0, + + enabled: (view, nodes, { folder }) => { + if (view.id !== 'trashbin') { + return false + } + + return nodes.length > 0 && folder.path === '/' + }, + + exec: async (view, nodes) => { + const dialog = getDialogBuilder(t('files_trashbin', 'Confirm permanent deletion')) + .setSeverity(DialogSeverity.Warning) + // TODO Add note for groupfolders + .setText(t('files_trashbin', "Are you sure you want to permanently delete the items in deleted files? You can't undo this action.")) + .setButtons([ + { + label: t('files_trashbin', 'Cancel'), + type: 'secondary', + callback: () => {}, + }, + { + label: t('files_trashbin', 'Empty deleted files'), + type: 'error', + callback: async () => { + for (const node of nodes) { + queue.add(async () => { + try { + await deleteNode(node) + } catch (error) { + logger.error('Failed to delete node', { error, node }) + } + }) + } + queue.once('empty', () => { + showSuccess(t('files_trashbin', 'Permanently deleted items in deleted files')) + }) + }, + }, + ]) + .build() + + try { + await dialog.show() + } catch (error) { + // Allow throw on dialog close + } + }, +}) diff --git a/apps/files_trashbin/src/files-init.ts b/apps/files_trashbin/src/files-init.ts index ab5d293d1369d..f516d6f5be552 100644 --- a/apps/files_trashbin/src/files-init.ts +++ b/apps/files_trashbin/src/files-init.ts @@ -6,6 +6,7 @@ import './trashbin.scss' import { translate as t } from '@nextcloud/l10n' +import { View, getNavigation, registerFileListAction } from '@nextcloud/files' import DeleteSvg from '@mdi/svg/svg/delete.svg?raw' import { getContents } from './services/trashbin' @@ -13,7 +14,8 @@ import { columns } from './columns.ts' // Register restore action import './actions/restoreAction' -import { View, getNavigation } from '@nextcloud/files' + +import { emptyTrashAction } from './fileListActions/emptyTrashAction.ts' const Navigation = getNavigation() Navigation.register(new View({ @@ -34,3 +36,5 @@ Navigation.register(new View({ getContents, })) + +registerFileListAction(emptyTrashAction) diff --git a/apps/files_trashbin/src/logger.ts b/apps/files_trashbin/src/logger.ts new file mode 100644 index 0000000000000..064351c2fb56c --- /dev/null +++ b/apps/files_trashbin/src/logger.ts @@ -0,0 +1,11 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { getLoggerBuilder } from '@nextcloud/logger' + +export const logger = getLoggerBuilder() + .setApp('files_trashbin') + .detectUser() + .build()