Skip to content

Commit 219f1f9

Browse files
authored
Merge pull request #45652 from nextcloud/feat/migrate-files-sharing-public-to-vue
feat(files_sharing): Migrate public shares to Vue
2 parents 117aaf4 + 6249125 commit 219f1f9

208 files changed

Lines changed: 2662 additions & 2161 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

__tests__/mock-window.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5-
import { beforeEach } from 'vitest'
6-
75
window.OC = { ...window.OC }
86
window.OCA = { ...window.OCA }
97
window.OCP = { ...window.OCP }
108

11-
beforeEach(() => {
12-
window.location = new URL('http://nextcloud.local')
13-
})
9+
window._oc_webroot = ''

apps/files/src/FilesApp.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
-->
55
<template>
66
<NcContent app-name="files">
7-
<Navigation />
8-
<FilesList />
7+
<Navigation v-if="!isPublic" />
8+
<FilesList :is-public="isPublic" />
99
</NcContent>
1010
</template>
1111

1212
<script lang="ts">
13+
import { isPublicShare } from '@nextcloud/sharing/public'
1314
import { defineComponent } from 'vue'
1415
1516
import NcContent from '@nextcloud/vue/dist/Components/NcContent.js'
@@ -25,5 +26,13 @@ export default defineComponent({
2526
FilesList,
2627
Navigation,
2728
},
29+
30+
setup() {
31+
const isPublic = isPublicShare()
32+
33+
return {
34+
isPublic,
35+
}
36+
},
2837
})
2938
</script>

apps/files/src/actions/downloadAction.ts

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5-
import type { ShareAttribute } from '../../../files_sharing/src/sharing'
6-
7-
import { FileAction, Permission, Node, FileType, View, DefaultType } from '@nextcloud/files'
5+
import { FileAction, Node, FileType, View, DefaultType } from '@nextcloud/files'
86
import { t } from '@nextcloud/l10n'
97
import { generateUrl } from '@nextcloud/router'
8+
import { getSharingToken, isPublicShare } from '@nextcloud/sharing/public'
9+
import { basename } from 'path'
10+
import { isDownloadable } from '../utils/permissions'
1011

1112
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
1213

@@ -19,29 +20,23 @@ const triggerDownload = function(url: string) {
1920

2021
const downloadNodes = function(dir: string, nodes: Node[]) {
2122
const secret = Math.random().toString(36).substring(2)
22-
const url = generateUrl('/apps/files/ajax/download.php?dir={dir}&files={files}&downloadStartSecret={secret}', {
23-
dir,
24-
secret,
25-
files: JSON.stringify(nodes.map(node => node.basename)),
26-
})
27-
triggerDownload(url)
28-
}
29-
30-
const isDownloadable = function(node: Node) {
31-
if ((node.permissions & Permission.READ) === 0) {
32-
return false
33-
}
34-
35-
// If the mount type is a share, ensure it got download permissions.
36-
if (node.attributes['mount-type'] === 'shared') {
37-
const shareAttributes = JSON.parse(node.attributes['share-attributes'] ?? '[]') as Array<ShareAttribute>
38-
const downloadAttribute = shareAttributes?.find?.((attribute: { scope: string; key: string }) => attribute.scope === 'permissions' && attribute.key === 'download')
39-
if (downloadAttribute !== undefined && downloadAttribute.value === false) {
40-
return false
41-
}
23+
let url: string
24+
if (isPublicShare()) {
25+
url = generateUrl('/s/{token}/download/{filename}?path={dir}&files={files}&downloadStartSecret={secret}', {
26+
dir,
27+
secret,
28+
files: JSON.stringify(nodes.map(node => node.basename)),
29+
token: getSharingToken(),
30+
filename: `${basename(dir)}.zip}`,
31+
})
32+
} else {
33+
url = generateUrl('/apps/files/ajax/download.php?dir={dir}&files={files}&downloadStartSecret={secret}', {
34+
dir,
35+
secret,
36+
files: JSON.stringify(nodes.map(node => node.basename)),
37+
})
4238
}
43-
44-
return true
39+
triggerDownload(url)
4540
}
4641

4742
export const action = new FileAction({

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ describe('Edit locally action execute tests', () => {
120120
data: { ocs: { data: { token: 'foobar' } } },
121121
}))
122122
const showError = vi.spyOn(nextcloudDialogs, 'showError')
123+
const windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
123124

124125
const file = new File({
125126
id: 1,
@@ -138,7 +139,7 @@ describe('Edit locally action execute tests', () => {
138139
expect(axios.post).toBeCalledTimes(1)
139140
expect(axios.post).toBeCalledWith('http://nextcloud.local/ocs/v2.php/apps/files/api/v1/openlocaleditor?format=json', { path: '/foobar.txt' })
140141
expect(showError).toBeCalledTimes(0)
141-
expect(window.location.href).toBe('nc://open/test@nextcloud.local/foobar.txt?token=foobar')
142+
expect(windowOpenSpy).toBeCalledWith('nc://open/test@nextcloud.local/foobar.txt?token=foobar', '_self')
142143
})
143144

144145
test('Edit locally fails and shows error', async () => {

apps/files/src/actions/editLocallyAction.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import axios from '@nextcloud/axios'
1212
import LaptopSvg from '@mdi/svg/svg/laptop.svg?raw'
1313
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
1414
import IconCheck from '@mdi/svg/svg/check.svg?raw'
15+
import { isPublicShare } from '@nextcloud/sharing/public'
1516

1617
const confirmLocalEditDialog = (
1718
localEditCallback: (openingLocally: boolean) => void = () => {},
@@ -72,7 +73,7 @@ const openLocalClient = async function(path: string) {
7273
let url = `nc://open/${uid}@` + window.location.host + encodePath(path)
7374
url += '?token=' + result.data.ocs.data.token
7475

75-
window.location.href = url
76+
window.open(url, '_self')
7677
} catch (error) {
7778
showError(t('files', 'Failed to redirect to client'))
7879
}
@@ -90,6 +91,11 @@ export const action = new FileAction({
9091
return false
9192
}
9293

94+
// does not work with shares
95+
if (isPublicShare()) {
96+
return false
97+
}
98+
9399
return (nodes[0].permissions & Permission.UPDATE) !== 0
94100
},
95101

apps/files/src/actions/favoriteAction.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5+
import type { Node, View } from '@nextcloud/files'
6+
57
import { emit } from '@nextcloud/event-bus'
6-
import { generateUrl } from '@nextcloud/router'
7-
import { Permission, type Node, View, FileAction } from '@nextcloud/files'
8+
import { Permission, FileAction } from '@nextcloud/files'
89
import { translate as t } from '@nextcloud/l10n'
10+
import { encodePath } from '@nextcloud/paths'
11+
import { generateUrl } from '@nextcloud/router'
12+
import { isPublicShare } from '@nextcloud/sharing/public'
913
import axios from '@nextcloud/axios'
1014
import Vue from 'vue'
1115

1216
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
1317
import StarSvg from '@mdi/svg/svg/star.svg?raw'
1418

1519
import logger from '../logger.ts'
16-
import { encodePath } from '@nextcloud/paths'
1720

1821
// If any of the nodes is not favorited, we display the favorite action.
1922
const shouldFavorite = (nodes: Node[]): boolean => {
@@ -69,8 +72,14 @@ export const action = new FileAction({
6972
},
7073

7174
enabled(nodes: Node[]) {
72-
// We can only favorite nodes within files and with permissions
73-
return !nodes.some(node => !node.root?.startsWith?.('/files'))
75+
// Not enabled for public shares
76+
if (isPublicShare()) {
77+
return false
78+
}
79+
80+
// We can only favorite nodes if they are located in files
81+
return nodes.every(node => node.root?.startsWith?.('/files'))
82+
// and we have permissions
7483
&& nodes.every(node => node.permissions !== Permission.NONE)
7584
},
7685

apps/files/src/actions/moveOrCopyAction.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,11 @@ export const action = new FileAction({
265265
}
266266
},
267267
iconSvgInline: () => FolderMoveSvg,
268-
enabled(nodes: Node[]) {
268+
enabled(nodes: Node[], view: View) {
269+
// We can not copy or move in single file shares
270+
if (view.id === 'public-file-share') {
271+
return false
272+
}
269273
// We only support moving/copying files within the user folder
270274
if (!nodes.every(node => node.root?.startsWith('/files/'))) {
271275
return false

apps/files/src/actions/moveOrCopyActionUtils.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import type { Folder, Node } from '@nextcloud/files'
77
import type { ShareAttribute } from '../../../files_sharing/src/sharing'
88

99
import { Permission } from '@nextcloud/files'
10+
import { isPublicShare } from '@nextcloud/sharing/public'
1011
import PQueue from 'p-queue'
12+
import { loadState } from '@nextcloud/initial-state'
13+
14+
const sharePermissions = loadState<number>('files_sharing', 'sharePermissions', Permission.NONE)
1115

1216
// This is the processing queue. We only want to allow 3 concurrent requests
1317
let queue: PQueue
@@ -51,7 +55,17 @@ export const canDownload = (nodes: Node[]) => {
5155

5256
export const canCopy = (nodes: Node[]) => {
5357
// a shared file cannot be copied if the download is disabled
54-
// it can be copied if the user has at least read permissions
55-
return canDownload(nodes)
56-
&& !nodes.some(node => node.permissions === Permission.NONE)
58+
if (!canDownload(nodes)) {
59+
return false
60+
}
61+
// it cannot be copied if the user has only view permissions
62+
if (nodes.some((node) => node.permissions === Permission.NONE)) {
63+
return false
64+
}
65+
// on public shares all files have the same permission so copy is only possible if write permission is granted
66+
if (isPublicShare()) {
67+
return Boolean(sharePermissions & Permission.CREATE)
68+
}
69+
// otherwise permission is granted
70+
return true
5771
}

apps/files/src/actions/renameAction.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55
import { emit } from '@nextcloud/event-bus'
6-
import { Permission, type Node, FileAction } from '@nextcloud/files'
6+
import { Permission, type Node, FileAction, View } from '@nextcloud/files'
77
import { translate as t } from '@nextcloud/l10n'
88
import PencilSvg from '@mdi/svg/svg/pencil.svg?raw'
99

@@ -14,10 +14,16 @@ export const action = new FileAction({
1414
displayName: () => t('files', 'Rename'),
1515
iconSvgInline: () => PencilSvg,
1616

17-
enabled: (nodes: Node[]) => {
18-
return nodes.length > 0 && nodes
19-
.map(node => node.permissions)
20-
.every(permission => Boolean(permission & Permission.DELETE))
17+
enabled: (nodes: Node[], view: View) => {
18+
if (nodes.length === 0) {
19+
return false
20+
}
21+
// Disable for single file shares
22+
if (view.id === 'public-file-share') {
23+
return false
24+
}
25+
// Only enable if all nodes have the delete permission
26+
return nodes.every((node) => Boolean(node.permissions & Permission.DELETE))
2127
},
2228

2329
async exec(node: Node) {

apps/files/src/actions/sidebarAction.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5-
import { Permission, type Node, View, FileAction } from '@nextcloud/files'
5+
import type { Node, View } from '@nextcloud/files'
6+
7+
import { Permission, FileAction } from '@nextcloud/files'
68
import { translate as t } from '@nextcloud/l10n'
9+
import { isPublicShare } from '@nextcloud/sharing/public'
10+
711
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
812

913
import logger from '../logger.ts'
@@ -17,6 +21,10 @@ export const action = new FileAction({
1721

1822
// Sidebar currently supports user folder only, /files/USER
1923
enabled: (nodes: Node[]) => {
24+
if (isPublicShare()) {
25+
return false
26+
}
27+
2028
// Only works on single node
2129
if (nodes.length !== 1) {
2230
return false

0 commit comments

Comments
 (0)