diff options
Diffstat (limited to 'apps/files/src/actions/deleteAction.ts')
-rw-r--r-- | apps/files/src/actions/deleteAction.ts | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/apps/files/src/actions/deleteAction.ts b/apps/files/src/actions/deleteAction.ts new file mode 100644 index 00000000000..fa4fdfe8cdc --- /dev/null +++ b/apps/files/src/actions/deleteAction.ts @@ -0,0 +1,113 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { Permission, Node, View, FileAction } from '@nextcloud/files' +import { loadState } from '@nextcloud/initial-state' +import PQueue from 'p-queue' + +import CloseSvg from '@mdi/svg/svg/close.svg?raw' +import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw' +import TrashCanSvg from '@mdi/svg/svg/trash-can-outline.svg?raw' + +import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts' +import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, shouldAskForConfirmation } from './deleteUtils.ts' +import logger from '../logger.ts' + +const queue = new PQueue({ concurrency: 5 }) + +export const ACTION_DELETE = 'delete' + +export const action = new FileAction({ + id: ACTION_DELETE, + displayName, + iconSvgInline: (nodes: Node[]) => { + if (canUnshareOnly(nodes)) { + return CloseSvg + } + + if (canDisconnectOnly(nodes)) { + return NetworkOffSvg + } + + return TrashCanSvg + }, + + enabled(nodes: Node[], view: View): boolean { + if (view.id === TRASHBIN_VIEW_ID) { + const config = loadState('files_trashbin', 'config', { allow_delete: true }) + if (config.allow_delete === false) { + return false + } + } + + return nodes.length > 0 && nodes + .map(node => node.permissions) + .every(permission => (permission & Permission.DELETE) !== 0) + }, + + async exec(node: Node, view: View) { + try { + let confirm = true + + // Trick to detect if the action was called from a keyboard event + // we need to make sure the method calling have its named containing 'keydown' + // here we use `onKeydown` method from the FileEntryActions component + const callStack = new Error().stack || '' + const isCalledFromEventListener = callStack.toLocaleLowerCase().includes('keydown') + + if (shouldAskForConfirmation() || isCalledFromEventListener) { + confirm = await askConfirmation([node], view) + } + + // If the user cancels the deletion, we don't want to do anything + if (confirm === false) { + return null + } + + await deleteNode(node) + + return true + } catch (error) { + logger.error('Error while deleting a file', { error, source: node.source, node }) + return false + } + }, + + async execBatch(nodes: Node[], view: View): Promise<(boolean | null)[]> { + let confirm = true + + if (shouldAskForConfirmation()) { + confirm = await askConfirmation(nodes, view) + } else if (nodes.length >= 5 && !canUnshareOnly(nodes) && !canDisconnectOnly(nodes)) { + confirm = await askConfirmation(nodes, view) + } + + // If the user cancels the deletion, we don't want to do anything + if (confirm === false) { + return Promise.all(nodes.map(() => null)) + } + + // Map each node to a promise that resolves with the result of exec(node) + const promises = nodes.map(node => { + // Create a promise that resolves with the result of exec(node) + const promise = new Promise<boolean>(resolve => { + queue.add(async () => { + try { + await deleteNode(node) + resolve(true) + } catch (error) { + logger.error('Error while deleting a file', { error, source: node.source, node }) + resolve(false) + } + }) + }) + return promise + }) + + return Promise.all(promises) + }, + + destructive: true, + order: 100, +}) |