summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2024-02-07 08:54:42 +0100
committerGitHub <noreply@github.com>2024-02-07 08:54:42 +0100
commit5dc2200ca02d4461ac61dd0e68bcbfa76068435c (patch)
treeefadc60815c2e7f7fe716e35bd9271384a548d9b /apps
parent13d0f5a34b07bcbe100815a905f7cb6091bb3829 (diff)
parent647b8746b0f1908178e935accef3c5de91b262cf (diff)
downloadnextcloud-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.php11
-rw-r--r--apps/files/src/actions/deleteAction.spec.ts81
-rw-r--r--apps/files/src/actions/deleteAction.ts93
-rw-r--r--apps/files/src/init.ts1
-rw-r--r--apps/files_sharing/src/init.ts5
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' })