Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/files/src/actions/deleteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Node, View } from '@nextcloud/files'

import CloseSvg from '@mdi/svg/svg/close.svg?raw'
Expand All @@ -11,10 +12,13 @@ import { FileAction, Permission } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import PQueue from 'p-queue'
import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
import logger from '../logger.ts'
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, shouldAskForConfirmation } from './deleteUtils.ts'

// TODO: once the files app is migrated to the new frontend use the import instead:
// import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
const TRASHBIN_VIEW_ID = 'trashbin'

const queue = new PQueue({ concurrency: 5 })

export const ACTION_DELETE = 'delete'
Expand Down
5 changes: 4 additions & 1 deletion apps/files_sharing/src/files_filters/AccountFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import { ShareType } from '@nextcloud/sharing'
import { isPublicShare } from '@nextcloud/sharing/public'
import Vue from 'vue'
import FileListFilterAccount from '../components/FileListFilterAccount.vue'
import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'

// once files_sharing is migrated to the new frontend use the import instead:
// import { TRASHBIN_VIEW_ID } from '../../../files_trashbin/src/files_views/trashbinView.ts'
const TRASHBIN_VIEW_ID = 'trashbin'

export interface IAccountData {
uid: string
Expand Down
21 changes: 15 additions & 6 deletions apps/files_trashbin/src/files_actions/restoreAction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ import * as ncEventBus from '@nextcloud/event-bus'
import { Folder } from '@nextcloud/files'
import isSvg from 'is-svg'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PERMISSION_ALL, PERMISSION_NONE } from '../../../../core/src/OC/constants.js'
import { trashbinView } from '../files_views/trashbinView.ts'
import { restoreAction } from './restoreAction.ts'

// TODO: once core is migrated to the new frontend use the import instead:
// import { PERMISSION_ALL, PERMISSION_NONE } from '../../../../core/src/OC/constants.js'
export const PERMISSION_NONE = 0
export const PERMISSION_ALL = 31

const axiosMock = vi.hoisted(() => ({
request: vi.fn(),
}))
vi.mock('@nextcloud/axios', () => ({ default: axiosMock }))
vi.mock('@nextcloud/axios', async (origial) => ({ ...(await origial()), default: axiosMock }))
vi.mock('@nextcloud/auth')

const errorSpy = vi.spyOn(window.console, 'error').mockImplementation(() => {})
beforeEach(() => errorSpy.mockClear())

describe('files_trashbin: file actions - restore action', () => {
it('has id set', () => {
expect(restoreAction.id).toBe('restore')
Expand Down Expand Up @@ -99,9 +106,9 @@ describe('files_trashbin: file actions - restore action', () => {

expect(await restoreAction.exec(node, trashbinView, '/')).toBe(true)
expect(axiosMock.request).toBeCalled()
expect(axiosMock.request.mock.calls[0][0].method).toBe('MOVE')
expect(axiosMock.request.mock.calls[0][0].url).toBe(node.encodedSource)
expect(axiosMock.request.mock.calls[0][0].headers.destination).toContain('/restore/')
expect(axiosMock.request.mock.calls[0]![0].method).toBe('MOVE')
expect(axiosMock.request.mock.calls[0]![0].url).toBe(node.encodedSource)
expect(axiosMock.request.mock.calls[0]![0].headers.destination).toContain('/restore/')
})

it('deletes node from current view after successfull request', async () => {
Expand All @@ -115,7 +122,7 @@ describe('files_trashbin: file actions - restore action', () => {
expect(emitSpy).toBeCalledWith('files:node:deleted', node)
})

it('does not delete node from view if reuest failed', async () => {
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 })

axiosMock.request.mockImplementationOnce(() => {
Expand All @@ -126,6 +133,7 @@ describe('files_trashbin: file actions - restore action', () => {
expect(await restoreAction.exec(node, trashbinView, '/')).toBe(false)
expect(axiosMock.request).toBeCalled()
expect(emitSpy).not.toBeCalled()
expect(errorSpy).toBeCalled()
})

it('batch: only returns success if all requests worked', async () => {
Expand All @@ -143,6 +151,7 @@ describe('files_trashbin: file actions - restore action', () => {
})
expect(await restoreAction.execBatch!([node, node], trashbinView, '/')).toStrictEqual([false, true])
expect(axiosMock.request).toBeCalledTimes(2)
expect(errorSpy).toBeCalled()
})
})
})
15 changes: 8 additions & 7 deletions apps/files_trashbin/src/files_actions/restoreAction.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import type { Node, View } from '@nextcloud/files'

import svgHistory from '@mdi/svg/svg/history.svg?raw'
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Node, View } from '@nextcloud/files'

import svgHistory from '@mdi/svg/svg/history.svg?raw'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import axios, { isAxiosError } from '@nextcloud/axios'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileAction, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { encodePath } from '@nextcloud/paths'
import { generateRemoteUrl } from '@nextcloud/router'
import logger from '../../../files/src/logger.ts'
import { TRASHBIN_VIEW_ID } from '../files_views/trashbinView.ts'
import { logger } from '../logger.ts'

export const restoreAction = new FileAction({
id: 'restore',
Expand Down Expand Up @@ -54,7 +55,7 @@ export const restoreAction = new FileAction({
emit('files:node:deleted', node)
return true
} catch (error) {
if (error.response?.status === 507) {
if (isAxiosError(error) && error.response?.status === 507) {
showError(t('files_trashbin', 'Not enough free space to restore the file/folder'))
}
logger.error('Failed to restore node', { error, node })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {

dialogBuilder.build.mockImplementationOnce(() => ({
show: async () => {
const buttons = dialogBuilder.setButtons.mock.calls[0][0]
const buttons = dialogBuilder.setButtons.mock.calls[0]![0]
const cancel = buttons.find(({ label }) => label === 'Cancel')
await cancel.callback()
},
Expand All @@ -142,7 +142,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {

dialogBuilder.build.mockImplementationOnce(() => ({
show: async () => {
const buttons = dialogBuilder.setButtons.mock.calls[0][0]
const buttons = dialogBuilder.setButtons.mock.calls[0]![0]
const cancel = buttons.find(({ label }) => label === 'Empty deleted files')
await cancel.callback()
},
Expand All @@ -160,7 +160,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {

dialogBuilder.build.mockImplementationOnce(() => ({
show: async () => {
const buttons = dialogBuilder.setButtons.mock.calls[0][0]
const buttons = dialogBuilder.setButtons.mock.calls[0]![0]
const cancel = buttons.find(({ label }) => label === 'Empty deleted files')
await cancel.callback()
},
Expand Down
4 changes: 2 additions & 2 deletions apps/files_trashbin/src/files_views/columns.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,14 @@ describe('files_trashbin: file list columns', () => {
const node = new File({ owner: 'test', source: 'https://example.com/remote.php/dav/files/test/a.txt', mime: 'text/plain', attributes: { 'trashbin-deleted-by-id': 'user-id' } })
const el: HTMLElement = deletedBy.render(node, trashbinView)
expect(el).toBeInstanceOf(HTMLElement)
expect(el.textContent).toMatch(/\suser-id\s/)
expect(el.textContent.trim()).toBe('user-id')
})

it('renders a node with deleting user display name', () => {
const node = new File({ owner: 'test', source: 'https://example.com/remote.php/dav/files/test/a.txt', mime: 'text/plain', attributes: { 'trashbin-deleted-by-display-name': 'user-name', 'trashbin-deleted-by-id': 'user-id' } })
const el: HTMLElement = deletedBy.render(node, trashbinView)
expect(el).toBeInstanceOf(HTMLElement)
expect(el.textContent).toMatch(/\suser-name\s/)
expect(el.textContent.trim()).toBe('user-name')
})

it('renders a node even when information is missing', () => {
Expand Down
11 changes: 5 additions & 6 deletions apps/files_trashbin/src/files_views/columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getCurrentUser } from '@nextcloud/auth'
import { Column } from '@nextcloud/files'
import { formatRelativeTime, getCanonicalLocale, getLanguage, t } from '@nextcloud/l10n'
import { dirname } from '@nextcloud/paths'
import Vue from 'vue'
import { createApp } from 'vue'
import NcUserBubble from '@nextcloud/vue/components/NcUserBubble'

export const originalLocation = new Column({
Expand Down Expand Up @@ -40,14 +40,13 @@ export const deletedBy = new Column({
return span
}

const UserBubble = Vue.extend(NcUserBubble)
const propsData = {
const el = document.createElement('div')
createApp(NcUserBubble, {
size: 32,
user: userId ?? undefined,
displayName: displayName ?? userId,
}
const userBubble = new UserBubble({ propsData }).$mount().$el
return userBubble as HTMLElement
}).mount(el)
return el
},
sort(nodeA, nodeB) {
const deletedByA = parseDeletedBy(nodeA)
Expand Down
5 changes: 3 additions & 2 deletions apps/files_trashbin/src/files_views/trashbinView.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import isSvg from 'is-svg'
/**
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import isSvg from 'is-svg'
import { describe, expect, it } from 'vitest'
import { getContents } from '../services/trashbin.ts'
import { deleted, deletedBy, originalLocation } from './columns.ts'
Expand Down
5 changes: 3 additions & 2 deletions apps/files_trashbin/src/files_views/trashbinView.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import svgDelete from '@mdi/svg/svg/trash-can-outline.svg?raw'
/**
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import svgDelete from '@mdi/svg/svg/trash-can-outline.svg?raw'
import { View } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { getContents } from '../services/trashbin.ts'
Expand Down
6 changes: 3 additions & 3 deletions apps/files_trashbin/src/logger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ describe('files_trashbin: logger', () => {

logger.error('<message>')
expect(consoleSpy).toBeCalledTimes(1)
expect(consoleSpy.mock.calls[0][0]).toContain('<message>')
expect(consoleSpy.mock.calls[0][0]).toContain('files_trashbin')
expect(consoleSpy.mock.calls[0][1].app).toBe('files_trashbin')
expect(consoleSpy.mock.calls[0]![0]).toContain('<message>')
expect(consoleSpy.mock.calls[0]![0]).toContain('files_trashbin')
expect(consoleSpy.mock.calls[0]![1].app).toBe('files_trashbin')
})
})
5 changes: 2 additions & 3 deletions apps/files_trashbin/src/services/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*/

import { getCurrentUser } from '@nextcloud/auth'
import { davGetClient } from '@nextcloud/files'
import { getClient } from '@nextcloud/files/dav'

// init webdav client
export const rootPath = `/trashbin/${getCurrentUser()?.uid}/trash`

export const client = davGetClient()
export const client = getClient()
16 changes: 10 additions & 6 deletions apps/files_trashbin/src/services/trashbin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { ContentsWithRoot, File, Folder } from '@nextcloud/files'
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { ContentsWithRoot, Folder, Node } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'

import { davResultToNode, getDavNameSpaces, getDavProperties } from '@nextcloud/files'
import { resultToNode as davResultToNode, getDavNameSpaces, getDavProperties } from '@nextcloud/files/dav'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep the original name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we already use that name below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we wrap it and export the wrapping function using that name)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, ok

import { generateUrl } from '@nextcloud/router'
import { client, rootPath } from './client.ts'

Expand All @@ -22,18 +23,21 @@ const data = `<?xml version="1.0"?>
</d:propfind>`

/**
* Converts a WebDAV file stat to a File or Folder
* This will fix the preview URL attribute for trashbin items
*
* @param stat
* @param stat - The file stat object from WebDAV response
*/
function resultToNode(stat: FileStat): File | Folder {
function resultToNode(stat: FileStat): Node {
const node = davResultToNode(stat, rootPath)
node.attributes.previewUrl = generateUrl('/apps/files_trashbin/preview?fileId={fileid}&x=32&y=32', { fileid: node.fileid })
return node
}

/**
* Get the contents of a trashbin folder
*
* @param path
* @param path - The path of the trashbin folder to get contents from
*/
export async function getContents(path = '/'): Promise<ContentsWithRoot> {
const contentsResponse = await client.getDirectoryContents(`${rootPath}${path}`, {
Expand Down
9 changes: 9 additions & 0 deletions apps/files_trashbin/src/shims.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare module '*?raw' {
const content: string
export default content
}
3 changes: 2 additions & 1 deletion apps/files_trashbin/src/trashbin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

.files-list__row-trashbin-original-location {
width: 150px !important;
width: 150px !important;
}
3 changes: 0 additions & 3 deletions build/frontend-legacy/webpack.modules.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ module.exports = {
'personal-settings': path.join(__dirname, 'apps/files_sharing/src', 'personal-settings.js'),
'public-nickname-handler': path.join(__dirname, 'apps/files_sharing/src', 'public-nickname-handler.ts'),
},
files_trashbin: {
init: path.join(__dirname, 'apps/files_trashbin/src', 'files-init.ts'),
},
oauth2: {
oauth2: path.join(__dirname, 'apps/oauth2/src', 'main.js'),
},
Expand Down
3 changes: 3 additions & 0 deletions build/frontend/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { createAppConfig } from '@nextcloud/vite-config'
import { resolve } from 'node:path'

const modules = {
files_trashbin: {
init: resolve(import.meta.dirname, 'apps/files_trashbin/src', 'files-init.ts'),
},
files_versions: {
'sidebar-tab': resolve(import.meta.dirname, 'apps/files_versions/src', 'sidebar_tab.ts'),
},
Expand Down
4 changes: 2 additions & 2 deletions dist/249-249.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/249-249.js.map

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions dist/NcSettingsSection-DFav6ob5-DccVMXCj.chunk.mjs

This file was deleted.

1 change: 0 additions & 1 deletion dist/NcSettingsSection-DFav6ob5-DccVMXCj.chunk.mjs.map

This file was deleted.

8 changes: 8 additions & 0 deletions dist/NcSettingsSection-DFav6ob5-DwBgvX10.chunk.mjs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/NcSettingsSection-DFav6ob5-DwBgvX10.chunk.mjs.map

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion dist/TrashCanOutline-BoqqfbiI.chunk.mjs.map

This file was deleted.

Loading
Loading