diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2024-02-07 08:54:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-07 08:54:42 +0100 |
commit | 5dc2200ca02d4461ac61dd0e68bcbfa76068435c (patch) | |
tree | efadc60815c2e7f7fe716e35bd9271384a548d9b /apps | |
parent | 13d0f5a34b07bcbe100815a905f7cb6091bb3829 (diff) | |
parent | 647b8746b0f1908178e935accef3c5de91b262cf (diff) | |
download | nextcloud-server-5dc2200ca02d4461ac61dd0e68bcbfa76068435c.tar.gz nextcloud-server-5dc2200ca02d4461ac61dd0e68bcbfa76068435c.zip |
Merge pull request #43404 from nextcloud/backport/43217/stable28
Diffstat (limited to 'apps')
-rw-r--r-- | apps/dav/lib/Connector/Sabre/FilesPlugin.php | 11 | ||||
-rw-r--r-- | apps/files/src/actions/deleteAction.spec.ts | 81 | ||||
-rw-r--r-- | apps/files/src/actions/deleteAction.ts | 93 | ||||
-rw-r--r-- | apps/files/src/init.ts | 1 | ||||
-rw-r--r-- | apps/files_sharing/src/init.ts | 5 |
5 files changed, 167 insertions, 24 deletions
diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index f7904e87883..4fed12e6569 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -79,6 +79,7 @@ class FilesPlugin extends ServerPlugin { public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint'; public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview'; public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type'; + public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root'; public const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted'; public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag'; public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time'; @@ -361,6 +362,16 @@ class FilesPlugin extends ServerPlugin { return $node->getFileInfo()->getMountPoint()->getMountType(); }); + /** + * This is a special property which is used to determine if a node + * is a mount root or not, e.g. a shared folder. + * If so, then the node can only be unshared and not deleted. + * @see https://github.com/nextcloud/server/blob/cc75294eb6b16b916a342e69998935f89222619d/lib/private/Files/View.php#L696-L698 + */ + $propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function () use ($node) { + return $node->getNode()->getInternalPath() === '' ? 'true' : 'false'; + }); + $propFind->handle(self::SHARE_NOTE, function () use ($node, $httpRequest): ?string { $user = $this->userSession->getUser(); if ($user === null) { diff --git a/apps/files/src/actions/deleteAction.spec.ts b/apps/files/src/actions/deleteAction.spec.ts index c568ec59d9d..0adb302dc32 100644 --- a/apps/files/src/actions/deleteAction.spec.ts +++ b/apps/files/src/actions/deleteAction.spec.ts @@ -22,9 +22,9 @@ import { action } from './deleteAction' import { expect } from '@jest/globals' import { File, Folder, Permission, View, FileAction } from '@nextcloud/files' -import * as auth from '@nextcloud/auth' import * as eventBus from '@nextcloud/event-bus' import axios from '@nextcloud/axios' + import logger from '../logger' const view = { @@ -50,36 +50,81 @@ describe('Delete action conditions tests', () => { permissions: Permission.ALL, }) - // const file2 = new File({ - // id: 1, - // source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', - // owner: 'admin', - // mime: 'text/plain', - // permissions: Permission.ALL, - // }) + const file2 = new File({ + id: 1, + source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + attributes: { + 'is-mount-root': true, + 'mount-type': 'shared', + }, + }) + + const folder = new Folder({ + id: 1, + source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + }) + + const folder2 = new Folder({ + id: 1, + source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + attributes: { + 'is-mount-root': true, + 'mount-type': 'shared', + }, + }) + + const folder3 = new Folder({ + id: 1, + source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo', + owner: 'admin', + mime: 'text/plain', + permissions: Permission.ALL, + attributes: { + 'is-mount-root': true, + 'mount-type': 'external', + }, + }) test('Default values', () => { expect(action).toBeInstanceOf(FileAction) expect(action.id).toBe('delete') - expect(action.displayName([file], view)).toBe('Delete') + expect(action.displayName([file], view)).toBe('Delete file') expect(action.iconSvgInline([], view)).toBe('<svg>SvgMock</svg>') expect(action.default).toBeUndefined() expect(action.order).toBe(100) }) - test('Default trashbin view values', () => { + test('Default folder displayName', () => { + expect(action.displayName([folder], view)).toBe('Delete folder') + }) + + test('Default trashbin view displayName', () => { expect(action.displayName([file], trashbinView)).toBe('Delete permanently') }) - // TODO: Fix this test - // test('Shared node values', () => { - // jest.spyOn(auth, 'getCurrentUser').mockReturnValue(null) - // expect(action.displayName([file2], view)).toBe('Unshare') - // }) + test('Shared root node displayName', () => { + expect(action.displayName([file2], view)).toBe('Leave this share') + expect(action.displayName([folder2], view)).toBe('Leave this share') + expect(action.displayName([file2, folder2], view)).toBe('Leave these shares') + }) + + test('External storage root node displayName', () => { + expect(action.displayName([folder3], view)).toBe('Disconnect storage') + expect(action.displayName([folder3, folder3], view)).toBe('Disconnect storages') + }) - // test('Shared and owned nodes values', () => { - // expect(action.displayName([file, file2], view)).toBe('Delete and unshare') - // }) + test('Shared and owned nodes displayName', () => { + expect(action.displayName([file, file2], view)).toBe('Delete and unshare') + }) }) describe('Delete action enabled tests', () => { diff --git a/apps/files/src/actions/deleteAction.ts b/apps/files/src/actions/deleteAction.ts index 1bc07aaa6f9..a086eb2e666 100644 --- a/apps/files/src/actions/deleteAction.ts +++ b/apps/files/src/actions/deleteAction.ts @@ -20,21 +20,102 @@ * */ import { emit } from '@nextcloud/event-bus' -import { Permission, Node, View, FileAction } from '@nextcloud/files' -import { translate as t } from '@nextcloud/l10n' +import { Permission, Node, View, FileAction, FileType } from '@nextcloud/files' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' import axios from '@nextcloud/axios' + +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.svg?raw' import logger from '../logger.js' +const canUnshareOnly = (nodes: Node[]) => { + return nodes.every(node => node.attributes['is-mount-root'] === true + && node.attributes['mount-type'] === 'shared') +} + +const canDisconnectOnly = (nodes: Node[]) => { + return nodes.every(node => node.attributes['is-mount-root'] === true + && node.attributes['mount-type'] === 'external') +} + +const isMixedUnshareAndDelete = (nodes: Node[]) => { + if (nodes.length === 1) { + return false + } + + const hasSharedItems = nodes.some(node => canUnshareOnly([node])) + const hasDeleteItems = nodes.some(node => !canUnshareOnly([node])) + return hasSharedItems && hasDeleteItems +} + +const isAllFiles = (nodes: Node[]) => { + return !nodes.some(node => node.type !== FileType.File) +} + +const isAllFolders = (nodes: Node[]) => { + return !nodes.some(node => node.type !== FileType.Folder) +} + export const action = new FileAction({ id: 'delete', displayName(nodes: Node[], view: View) { - return view.id === 'trashbin' - ? t('files', 'Delete permanently') - : t('files', 'Delete') + /** + * If we're in the trashbin, we can only delete permanently + */ + if (view.id === 'trashbin') { + return t('files', 'Delete permanently') + } + + /** + * If we're in the sharing view, we can only unshare + */ + if (isMixedUnshareAndDelete(nodes)) { + return t('files', 'Delete and unshare') + } + + /** + * If those nodes are all the root node of a + * share, we can only unshare them. + */ + if (canUnshareOnly(nodes)) { + return n('files', 'Leave this share', 'Leave these shares', nodes.length) + } + + /** + * If those nodes are all the root node of an + * external storage, we can only disconnect it. + */ + if (canDisconnectOnly(nodes)) { + return n('files', 'Disconnect storage', 'Disconnect storages', nodes.length) + } + + /** + * If we're only selecting files, use proper wording + */ + if (isAllFiles(nodes)) { + return n('files', 'Delete file', 'Delete files', nodes.length) + } + + /** + * If we're only selecting folders, use proper wording + */ + if (isAllFolders(nodes)) { + return n('files', 'Delete folder', 'Delete folders', nodes.length) + } + + return t('files', 'Delete') }, - iconSvgInline: () => { + iconSvgInline: (nodes: Node[]) => { + if (canUnshareOnly(nodes)) { + return CloseSvg + } + + if (canDisconnectOnly(nodes)) { + return NetworkOffSvg + } + return TrashCanSvg }, diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index 8002f33ff56..9f463244d91 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -66,5 +66,6 @@ registerRecentView() registerPreviewServiceWorker() registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' }) +registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' }) initLivePhotos() diff --git a/apps/files_sharing/src/init.ts b/apps/files_sharing/src/init.ts index 734f888cb7d..7699fcff9af 100644 --- a/apps/files_sharing/src/init.ts +++ b/apps/files_sharing/src/init.ts @@ -20,6 +20,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ +import { registerDavProperty } from '@nextcloud/files' import registerSharingViews from './views/shares' import './actions/acceptShareAction' @@ -29,3 +30,7 @@ import './actions/restoreShareAction' import './actions/sharingStatusAction' registerSharingViews() + +registerDavProperty('nc:share-attributes', { nc: 'http://nextcloud.org/ns' }) +registerDavProperty('oc:share-types', { oc: 'http://owncloud.org/ns' }) +registerDavProperty('ocs:share-permissions', { ocs: 'http://open-collaboration-services.org/ns' }) |