Skip to content

Commit c5b3768

Browse files
authored
Merge pull request #53900 from nextcloud/feat/ask-deletion
2 parents 19009b3 + 2e0f33d commit c5b3768

File tree

11 files changed

+45
-18
lines changed

11 files changed

+45
-18
lines changed

apps/files/lib/Service/UserConfig.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ class UserConfig {
3737
'default' => false,
3838
'allowed' => [true, false],
3939
],
40+
[
41+
// Whether to show the "confirm file deletion" warning
42+
'key' => 'show_dialog_deletion',
43+
'default' => false,
44+
'allowed' => [true, false],
45+
],
4046
[
4147
// Whether to show the "confirm file extension change" warning
4248
'key' => 'show_dialog_file_extension',

apps/files/src/actions/deleteAction.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as eventBus from '@nextcloud/event-bus'
1111

1212
import { action } from './deleteAction'
1313
import logger from '../logger'
14+
import { shouldAskForConfirmation } from './deleteUtils'
1415

1516
vi.mock('@nextcloud/auth')
1617
vi.mock('@nextcloud/axios')
@@ -235,7 +236,6 @@ describe('Delete action execute tests', () => {
235236
vi.spyOn(eventBus, 'emit')
236237

237238
const confirmMock = vi.fn()
238-
// @ts-expect-error We only mock what needed
239239
window.OC = { dialogs: { confirmDestructive: confirmMock } }
240240

241241
const file1 = new File({
@@ -275,7 +275,6 @@ describe('Delete action execute tests', () => {
275275

276276
// Emulate the confirmation dialog to always confirm
277277
const confirmMock = vi.fn().mockImplementation((a, b, c, resolve) => resolve(true))
278-
// @ts-expect-error We only mock what needed
279278
window.OC = { dialogs: { confirmDestructive: confirmMock } }
280279

281280
const file1 = new File({
@@ -339,7 +338,11 @@ describe('Delete action execute tests', () => {
339338
expect(eventBus.emit).toHaveBeenNthCalledWith(5, 'files:node:deleted', file5)
340339
})
341340

342-
test('Delete action batch trashbin disabled', async () => {
341+
test('Delete action batch dialog enabled', async () => {
342+
// Enable the confirmation dialog
343+
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: true })
344+
expect(shouldAskForConfirmation()).toBe(true)
345+
343346
vi.spyOn(axios, 'delete')
344347
vi.spyOn(eventBus, 'emit')
345348
vi.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
@@ -350,7 +353,6 @@ describe('Delete action execute tests', () => {
350353

351354
// Emulate the confirmation dialog to always confirm
352355
const confirmMock = vi.fn().mockImplementation((a, b, c, resolve) => resolve(true))
353-
// @ts-expect-error We only mock what needed
354356
window.OC = { dialogs: { confirmDestructive: confirmMock } }
355357

356358
const file1 = new File({
@@ -382,6 +384,8 @@ describe('Delete action execute tests', () => {
382384
expect(eventBus.emit).toBeCalledTimes(2)
383385
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
384386
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
387+
388+
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: false })
385389
})
386390

387391
test('Delete fails', async () => {
@@ -407,7 +411,10 @@ describe('Delete action execute tests', () => {
407411
expect(logger.error).toBeCalledTimes(1)
408412
})
409413

410-
test('Delete is cancelled', async () => {
414+
test('Delete is cancelled with dialog enabled', async () => {
415+
// Enable the confirmation dialog
416+
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: true })
417+
411418
vi.spyOn(axios, 'delete')
412419
vi.spyOn(eventBus, 'emit')
413420
vi.spyOn(capabilities, 'getCapabilities').mockImplementation(() => {
@@ -418,7 +425,6 @@ describe('Delete action execute tests', () => {
418425

419426
// Emulate the confirmation dialog to always confirm
420427
const confirmMock = vi.fn().mockImplementation((a, b, c, resolve) => resolve(false))
421-
// @ts-expect-error We only mock what needed
422428
window.OC = { dialogs: { confirmDestructive: confirmMock } }
423429

424430
const file1 = new File({
@@ -437,5 +443,7 @@ describe('Delete action execute tests', () => {
437443
expect(axios.delete).toBeCalledTimes(0)
438444

439445
expect(eventBus.emit).toBeCalledTimes(0)
446+
447+
eventBus.emit('files:config:updated', { key: 'show_dialog_deletion', value: false })
440448
})
441449
})

apps/files/src/actions/deleteAction.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw'
1313
import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw'
1414

1515
import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
16-
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, isTrashbinEnabled } from './deleteUtils.ts'
16+
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, shouldAskForConfirmation } from './deleteUtils.ts'
1717
import logger from '../logger.ts'
1818

1919
const queue = new PQueue({ concurrency: 5 })
@@ -58,8 +58,7 @@ export const action = new FileAction({
5858
const callStack = new Error().stack || ''
5959
const isCalledFromEventListener = callStack.toLocaleLowerCase().includes('keydown')
6060

61-
// If trashbin is disabled, we need to ask for confirmation
62-
if (!isTrashbinEnabled() || isCalledFromEventListener) {
61+
if (shouldAskForConfirmation() || isCalledFromEventListener) {
6362
confirm = await askConfirmation([node], view)
6463
}
6564

@@ -81,8 +80,7 @@ export const action = new FileAction({
8180
async execBatch(nodes: Node[], view: View): Promise<(boolean | null)[]> {
8281
let confirm = true
8382

84-
// If trashbin is disabled, we need to ask for confirmation
85-
if (!isTrashbinEnabled()) {
83+
if (shouldAskForConfirmation()) {
8684
confirm = await askConfirmation(nodes, view)
8785
} else if (nodes.length >= 5 && !canUnshareOnly(nodes) && !canDisconnectOnly(nodes)) {
8886
confirm = await askConfirmation(nodes, view)

apps/files/src/actions/deleteUtils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { FileType } from '@nextcloud/files'
1010
import { getCapabilities } from '@nextcloud/capabilities'
1111
import { n, t } from '@nextcloud/l10n'
1212
import axios from '@nextcloud/axios'
13+
import { useUserConfigStore } from '../store/userconfig'
14+
import { getPinia } from '../store'
1315

1416
export const isTrashbinEnabled = () => (getCapabilities() as Capabilities)?.files?.undelete === true
1517

@@ -101,6 +103,11 @@ export const displayName = (nodes: Node[], view: View) => {
101103
return t('files', 'Delete')
102104
}
103105

106+
export const shouldAskForConfirmation = () => {
107+
const userConfig = useUserConfigStore(getPinia())
108+
return userConfig.userConfig.show_dialog_deletion !== false
109+
}
110+
104111
export const askConfirmation = async (nodes: Node[], view: View) => {
105112
const message = view.id === 'trashbin' || !isTrashbinEnabled()
106113
? n('files', 'You are about to permanently delete {count} item', 'You are about to permanently delete {count} items', nodes.length, { count: nodes.length })

apps/files/src/store/userconfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const initialUserConfig = loadState<UserConfig>('files', 'config', {
2020
sort_favorites_first: true,
2121
sort_folders_first: true,
2222

23+
show_dialog_deletion: false,
2324
show_dialog_file_extension: true,
2425
})
2526

apps/files/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ export interface UserConfig {
5555
crop_image_previews: boolean
5656
default_view: 'files' | 'personal'
5757
grid_view: boolean
58-
show_dialog_file_extension: boolean,
5958
show_hidden: boolean
6059
show_mime_column: boolean
6160
sort_favorites_first: boolean
6261
sort_folders_first: boolean
62+
63+
show_dialog_deletion: boolean
64+
show_dialog_file_extension: boolean,
6365
}
6466

6567
export interface UserConfigStore {

apps/files/src/views/Settings.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@
110110
@update:checked="setConfig('show_dialog_file_extension', $event)">
111111
{{ t('files', 'Show a warning dialog when changing a file extension.') }}
112112
</NcCheckboxRadioSwitch>
113+
<NcCheckboxRadioSwitch type="switch"
114+
:checked="userConfig.show_dialog_deletion"
115+
@update:checked="setConfig('show_dialog_deletion', $event)">
116+
{{ t('files', 'Show a warning dialog when deleting files.') }}
117+
</NcCheckboxRadioSwitch>
113118
</NcAppSettingsSection>
114119

115120
<NcAppSettingsSection id="shortcuts"

dist/files-init.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-init.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)