Skip to content

Commit 647b874

Browse files
skjnldsvnextcloud-command
authored andcommitted
fix(files): add mount root property and adjust delete wording
Signed-off-by: John Molakvoæ <[email protected]> Signed-off-by: nextcloud-command <[email protected]>
1 parent 2f6b641 commit 647b874

File tree

9 files changed

+173
-30
lines changed

9 files changed

+173
-30
lines changed

apps/dav/lib/Connector/Sabre/FilesPlugin.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class FilesPlugin extends ServerPlugin {
7979
public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
8080
public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
8181
public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
82+
public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root';
8283
public const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
8384
public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
8485
public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
@@ -361,6 +362,16 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node)
361362
return $node->getFileInfo()->getMountPoint()->getMountType();
362363
});
363364

365+
/**
366+
* This is a special property which is used to determine if a node
367+
* is a mount root or not, e.g. a shared folder.
368+
* If so, then the node can only be unshared and not deleted.
369+
* @see https://github.com/nextcloud/server/blob/cc75294eb6b16b916a342e69998935f89222619d/lib/private/Files/View.php#L696-L698
370+
*/
371+
$propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function () use ($node) {
372+
return $node->getNode()->getInternalPath() === '' ? 'true' : 'false';
373+
});
374+
364375
$propFind->handle(self::SHARE_NOTE, function () use ($node, $httpRequest): ?string {
365376
$user = $this->userSession->getUser();
366377
if ($user === null) {

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

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
import { action } from './deleteAction'
2323
import { expect } from '@jest/globals'
2424
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
25-
import * as auth from '@nextcloud/auth'
2625
import * as eventBus from '@nextcloud/event-bus'
2726
import axios from '@nextcloud/axios'
27+
2828
import logger from '../logger'
2929

3030
const view = {
@@ -50,36 +50,81 @@ describe('Delete action conditions tests', () => {
5050
permissions: Permission.ALL,
5151
})
5252

53-
// const file2 = new File({
54-
// id: 1,
55-
// source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
56-
// owner: 'admin',
57-
// mime: 'text/plain',
58-
// permissions: Permission.ALL,
59-
// })
53+
const file2 = new File({
54+
id: 1,
55+
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
56+
owner: 'admin',
57+
mime: 'text/plain',
58+
permissions: Permission.ALL,
59+
attributes: {
60+
'is-mount-root': true,
61+
'mount-type': 'shared',
62+
},
63+
})
64+
65+
const folder = new Folder({
66+
id: 1,
67+
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo',
68+
owner: 'admin',
69+
mime: 'text/plain',
70+
permissions: Permission.ALL,
71+
})
72+
73+
const folder2 = new Folder({
74+
id: 1,
75+
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo',
76+
owner: 'admin',
77+
mime: 'text/plain',
78+
permissions: Permission.ALL,
79+
attributes: {
80+
'is-mount-root': true,
81+
'mount-type': 'shared',
82+
},
83+
})
84+
85+
const folder3 = new Folder({
86+
id: 1,
87+
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo',
88+
owner: 'admin',
89+
mime: 'text/plain',
90+
permissions: Permission.ALL,
91+
attributes: {
92+
'is-mount-root': true,
93+
'mount-type': 'external',
94+
},
95+
})
6096

6197
test('Default values', () => {
6298
expect(action).toBeInstanceOf(FileAction)
6399
expect(action.id).toBe('delete')
64-
expect(action.displayName([file], view)).toBe('Delete')
100+
expect(action.displayName([file], view)).toBe('Delete file')
65101
expect(action.iconSvgInline([], view)).toBe('<svg>SvgMock</svg>')
66102
expect(action.default).toBeUndefined()
67103
expect(action.order).toBe(100)
68104
})
69105

70-
test('Default trashbin view values', () => {
106+
test('Default folder displayName', () => {
107+
expect(action.displayName([folder], view)).toBe('Delete folder')
108+
})
109+
110+
test('Default trashbin view displayName', () => {
71111
expect(action.displayName([file], trashbinView)).toBe('Delete permanently')
72112
})
73113

74-
// TODO: Fix this test
75-
// test('Shared node values', () => {
76-
// jest.spyOn(auth, 'getCurrentUser').mockReturnValue(null)
77-
// expect(action.displayName([file2], view)).toBe('Unshare')
78-
// })
114+
test('Shared root node displayName', () => {
115+
expect(action.displayName([file2], view)).toBe('Leave this share')
116+
expect(action.displayName([folder2], view)).toBe('Leave this share')
117+
expect(action.displayName([file2, folder2], view)).toBe('Leave these shares')
118+
})
119+
120+
test('External storage root node displayName', () => {
121+
expect(action.displayName([folder3], view)).toBe('Disconnect storage')
122+
expect(action.displayName([folder3, folder3], view)).toBe('Disconnect storages')
123+
})
79124

80-
// test('Shared and owned nodes values', () => {
81-
// expect(action.displayName([file, file2], view)).toBe('Delete and unshare')
82-
// })
125+
test('Shared and owned nodes displayName', () => {
126+
expect(action.displayName([file, file2], view)).toBe('Delete and unshare')
127+
})
83128
})
84129

85130
describe('Delete action enabled tests', () => {

apps/files/src/actions/deleteAction.ts

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,102 @@
2020
*
2121
*/
2222
import { emit } from '@nextcloud/event-bus'
23-
import { Permission, Node, View, FileAction } from '@nextcloud/files'
24-
import { translate as t } from '@nextcloud/l10n'
23+
import { Permission, Node, View, FileAction, FileType } from '@nextcloud/files'
24+
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
2525
import axios from '@nextcloud/axios'
26+
27+
import CloseSvg from '@mdi/svg/svg/close.svg?raw'
28+
import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw'
2629
import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw'
2730

2831
import logger from '../logger.js'
2932

33+
const canUnshareOnly = (nodes: Node[]) => {
34+
return nodes.every(node => node.attributes['is-mount-root'] === true
35+
&& node.attributes['mount-type'] === 'shared')
36+
}
37+
38+
const canDisconnectOnly = (nodes: Node[]) => {
39+
return nodes.every(node => node.attributes['is-mount-root'] === true
40+
&& node.attributes['mount-type'] === 'external')
41+
}
42+
43+
const isMixedUnshareAndDelete = (nodes: Node[]) => {
44+
if (nodes.length === 1) {
45+
return false
46+
}
47+
48+
const hasSharedItems = nodes.some(node => canUnshareOnly([node]))
49+
const hasDeleteItems = nodes.some(node => !canUnshareOnly([node]))
50+
return hasSharedItems && hasDeleteItems
51+
}
52+
53+
const isAllFiles = (nodes: Node[]) => {
54+
return !nodes.some(node => node.type !== FileType.File)
55+
}
56+
57+
const isAllFolders = (nodes: Node[]) => {
58+
return !nodes.some(node => node.type !== FileType.Folder)
59+
}
60+
3061
export const action = new FileAction({
3162
id: 'delete',
3263
displayName(nodes: Node[], view: View) {
33-
return view.id === 'trashbin'
34-
? t('files', 'Delete permanently')
35-
: t('files', 'Delete')
64+
/**
65+
* If we're in the trashbin, we can only delete permanently
66+
*/
67+
if (view.id === 'trashbin') {
68+
return t('files', 'Delete permanently')
69+
}
70+
71+
/**
72+
* If we're in the sharing view, we can only unshare
73+
*/
74+
if (isMixedUnshareAndDelete(nodes)) {
75+
return t('files', 'Delete and unshare')
76+
}
77+
78+
/**
79+
* If those nodes are all the root node of a
80+
* share, we can only unshare them.
81+
*/
82+
if (canUnshareOnly(nodes)) {
83+
return n('files', 'Leave this share', 'Leave these shares', nodes.length)
84+
}
85+
86+
/**
87+
* If those nodes are all the root node of an
88+
* external storage, we can only disconnect it.
89+
*/
90+
if (canDisconnectOnly(nodes)) {
91+
return n('files', 'Disconnect storage', 'Disconnect storages', nodes.length)
92+
}
93+
94+
/**
95+
* If we're only selecting files, use proper wording
96+
*/
97+
if (isAllFiles(nodes)) {
98+
return n('files', 'Delete file', 'Delete files', nodes.length)
99+
}
100+
101+
/**
102+
* If we're only selecting folders, use proper wording
103+
*/
104+
if (isAllFolders(nodes)) {
105+
return n('files', 'Delete folder', 'Delete folders', nodes.length)
106+
}
107+
108+
return t('files', 'Delete')
36109
},
37-
iconSvgInline: () => {
110+
iconSvgInline: (nodes: Node[]) => {
111+
if (canUnshareOnly(nodes)) {
112+
return CloseSvg
113+
}
114+
115+
if (canDisconnectOnly(nodes)) {
116+
return NetworkOffSvg
117+
}
118+
38119
return TrashCanSvg
39120
},
40121

apps/files/src/init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ registerRecentView()
6666
registerPreviewServiceWorker()
6767

6868
registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' })
69+
registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })
6970

7071
initLivePhotos()

apps/files_sharing/src/init.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* along with this program. If not, see <http://www.gnu.org/licenses/>.
2121
*
2222
*/
23+
import { registerDavProperty } from '@nextcloud/files'
2324
import registerSharingViews from './views/shares'
2425

2526
import './actions/acceptShareAction'
@@ -29,3 +30,7 @@ import './actions/restoreShareAction'
2930
import './actions/sharingStatusAction'
3031

3132
registerSharingViews()
33+
34+
registerDavProperty('nc:share-attributes', { nc: 'http://nextcloud.org/ns' })
35+
registerDavProperty('oc:share-types', { oc: 'http://owncloud.org/ns' })
36+
registerDavProperty('ocs:share-permissions', { ocs: 'http://open-collaboration-services.org/ns' })

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_sharing-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_sharing-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.

0 commit comments

Comments
 (0)