mirror of
https://github.com/nextcloud/server.git
synced 2026-02-18 18:28:50 -05:00
feat(files): add delete confirmation option
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
parent
19009b3620
commit
f89660e709
7 changed files with 39 additions and 12 deletions
|
|
@ -37,6 +37,12 @@ class UserConfig {
|
|||
'default' => false,
|
||||
'allowed' => [true, false],
|
||||
],
|
||||
[
|
||||
// Whether to show the "confirm file deletion" warning
|
||||
'key' => 'show_dialog_deletion',
|
||||
'default' => false,
|
||||
'allowed' => [true, false],
|
||||
],
|
||||
[
|
||||
// Whether to show the "confirm file extension change" warning
|
||||
'key' => 'show_dialog_file_extension',
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import * as eventBus from '@nextcloud/event-bus'
|
|||
|
||||
import { action } from './deleteAction'
|
||||
import logger from '../logger'
|
||||
import { shouldAskForConfirmation } from './deleteUtils'
|
||||
|
||||
vi.mock('@nextcloud/auth')
|
||||
vi.mock('@nextcloud/axios')
|
||||
|
|
@ -235,7 +236,6 @@ describe('Delete action execute tests', () => {
|
|||
vi.spyOn(eventBus, 'emit')
|
||||
|
||||
const confirmMock = vi.fn()
|
||||
// @ts-expect-error We only mock what needed
|
||||
window.OC = { dialogs: { confirmDestructive: confirmMock } }
|
||||
|
||||
const file1 = new File({
|
||||
|
|
@ -275,7 +275,6 @@ describe('Delete action execute tests', () => {
|
|||
|
||||
// Emulate the confirmation dialog to always confirm
|
||||
const confirmMock = vi.fn().mockImplementation((a, b, c, resolve) => resolve(true))
|
||||
// @ts-expect-error We only mock what needed
|
||||
window.OC = { dialogs: { confirmDestructive: confirmMock } }
|
||||
|
||||
const file1 = new File({
|
||||
|
|
@ -339,7 +338,11 @@ describe('Delete action execute tests', () => {
|
|||
expect(eventBus.emit).toHaveBeenNthCalledWith(5, 'files:node:deleted', file5)
|
||||
})
|
||||
|
||||
test('Delete action batch trashbin disabled', async () => {
|
||||
test('Delete action batch dialog enabled', async () => {
|
||||
// Enable the confirmation dialog
|
||||
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: true })
|
||||
expect(shouldAskForConfirmation()).toBe(true)
|
||||
|
||||
vi.spyOn(axios, 'delete')
|
||||
vi.spyOn(eventBus, 'emit')
|
||||
vi.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
|
||||
|
|
@ -350,7 +353,6 @@ describe('Delete action execute tests', () => {
|
|||
|
||||
// Emulate the confirmation dialog to always confirm
|
||||
const confirmMock = vi.fn().mockImplementation((a, b, c, resolve) => resolve(true))
|
||||
// @ts-expect-error We only mock what needed
|
||||
window.OC = { dialogs: { confirmDestructive: confirmMock } }
|
||||
|
||||
const file1 = new File({
|
||||
|
|
@ -382,6 +384,8 @@ describe('Delete action execute tests', () => {
|
|||
expect(eventBus.emit).toBeCalledTimes(2)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
|
||||
|
||||
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: false })
|
||||
})
|
||||
|
||||
test('Delete fails', async () => {
|
||||
|
|
@ -407,7 +411,10 @@ describe('Delete action execute tests', () => {
|
|||
expect(logger.error).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('Delete is cancelled', async () => {
|
||||
test('Delete is cancelled with dialog enabled', async () => {
|
||||
// Enable the confirmation dialog
|
||||
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: true })
|
||||
|
||||
vi.spyOn(axios, 'delete')
|
||||
vi.spyOn(eventBus, 'emit')
|
||||
vi.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
|
||||
|
|
@ -418,7 +425,6 @@ describe('Delete action execute tests', () => {
|
|||
|
||||
// Emulate the confirmation dialog to always confirm
|
||||
const confirmMock = vi.fn().mockImplementation((a, b, c, resolve) => resolve(false))
|
||||
// @ts-expect-error We only mock what needed
|
||||
window.OC = { dialogs: { confirmDestructive: confirmMock } }
|
||||
|
||||
const file1 = new File({
|
||||
|
|
@ -437,5 +443,7 @@ describe('Delete action execute tests', () => {
|
|||
expect(axios.delete).toBeCalledTimes(0)
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(0)
|
||||
|
||||
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: false })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw'
|
|||
import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw'
|
||||
|
||||
import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
|
||||
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, isTrashbinEnabled } from './deleteUtils.ts'
|
||||
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, shouldAskForConfirmation } from './deleteUtils.ts'
|
||||
import logger from '../logger.ts'
|
||||
|
||||
const queue = new PQueue({ concurrency: 5 })
|
||||
|
|
@ -58,8 +58,7 @@ export const action = new FileAction({
|
|||
const callStack = new Error().stack || ''
|
||||
const isCalledFromEventListener = callStack.toLocaleLowerCase().includes('keydown')
|
||||
|
||||
// If trashbin is disabled, we need to ask for confirmation
|
||||
if (!isTrashbinEnabled() || isCalledFromEventListener) {
|
||||
if (shouldAskForConfirmation() || isCalledFromEventListener) {
|
||||
confirm = await askConfirmation([node], view)
|
||||
}
|
||||
|
||||
|
|
@ -81,8 +80,7 @@ export const action = new FileAction({
|
|||
async execBatch(nodes: Node[], view: View): Promise<(boolean | null)[]> {
|
||||
let confirm = true
|
||||
|
||||
// If trashbin is disabled, we need to ask for confirmation
|
||||
if (!isTrashbinEnabled()) {
|
||||
if (shouldAskForConfirmation()) {
|
||||
confirm = await askConfirmation(nodes, view)
|
||||
} else if (nodes.length >= 5 && !canUnshareOnly(nodes) && !canDisconnectOnly(nodes)) {
|
||||
confirm = await askConfirmation(nodes, view)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { FileType } from '@nextcloud/files'
|
|||
import { getCapabilities } from '@nextcloud/capabilities'
|
||||
import { n, t } from '@nextcloud/l10n'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { useUserConfigStore } from '../store/userconfig'
|
||||
import { getPinia } from '../store'
|
||||
|
||||
export const isTrashbinEnabled = () => (getCapabilities() as Capabilities)?.files?.undelete === true
|
||||
|
||||
|
|
@ -101,6 +103,11 @@ export const displayName = (nodes: Node[], view: View) => {
|
|||
return t('files', 'Delete')
|
||||
}
|
||||
|
||||
export const shouldAskForConfirmation = () => {
|
||||
const userConfig = useUserConfigStore(getPinia())
|
||||
return userConfig.userConfig.show_dialog_deletion !== false
|
||||
}
|
||||
|
||||
export const askConfirmation = async (nodes: Node[], view: View) => {
|
||||
const message = view.id === 'trashbin' || !isTrashbinEnabled()
|
||||
? n('files', 'You are about to permanently delete {count} item', 'You are about to permanently delete {count} items', nodes.length, { count: nodes.length })
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const initialUserConfig = loadState<UserConfig>('files', 'config', {
|
|||
sort_favorites_first: true,
|
||||
sort_folders_first: true,
|
||||
|
||||
show_dialog_deletion: false,
|
||||
show_dialog_file_extension: true,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -55,11 +55,13 @@ export interface UserConfig {
|
|||
crop_image_previews: boolean
|
||||
default_view: 'files' | 'personal'
|
||||
grid_view: boolean
|
||||
show_dialog_file_extension: boolean,
|
||||
show_hidden: boolean
|
||||
show_mime_column: boolean
|
||||
sort_favorites_first: boolean
|
||||
sort_folders_first: boolean
|
||||
|
||||
show_dialog_deletion: boolean
|
||||
show_dialog_file_extension: boolean,
|
||||
}
|
||||
|
||||
export interface UserConfigStore {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@
|
|||
@update:checked="setConfig('show_dialog_file_extension', $event)">
|
||||
{{ t('files', 'Show a warning dialog when changing a file extension.') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcCheckboxRadioSwitch type="switch"
|
||||
:checked="userConfig.show_dialog_deletion"
|
||||
@update:checked="setConfig('show_dialog_deletion', $event)">
|
||||
{{ t('files', 'Show a warning dialog when deleting files.') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
</NcAppSettingsSection>
|
||||
|
||||
<NcAppSettingsSection id="shortcuts"
|
||||
|
|
|
|||
Loading…
Reference in a new issue