Browse Source
feat(files_sharing): Migrate public share to use Vue files list
feat(files_sharing): Migrate public share to use Vue files list
Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de> Co-authored-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com> Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/45652/head
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
15 changed files with 614 additions and 542 deletions
-
8apps/files/src/main.ts
-
11apps/files/src/services/Files.ts
-
8apps/files/src/services/RouterService.ts
-
7apps/files/src/views/Sidebar.vue
-
303apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
-
28apps/files_sharing/src/init-public.ts
-
54apps/files_sharing/src/router/index.ts
-
4apps/files_sharing/src/services/SharingService.spec.ts
-
13apps/files_sharing/src/services/SharingService.ts
-
67apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue
-
59apps/files_sharing/src/views/publicFileDrop.ts
-
67apps/files_sharing/src/views/publicFileShare.ts
-
28apps/files_sharing/src/views/publicShare.ts
-
498apps/files_sharing/tests/Controller/ShareControllerTest.php
-
1webpack.modules.js
@ -0,0 +1,28 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
import { getNavigation, registerDavProperty } from '@nextcloud/files' |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import registerFileDropView from './views/publicFileDrop.ts' |
|||
import registerPublicShareView from './views/publicShare.ts' |
|||
import registerPublicFileShareView from './views/publicFileShare.ts' |
|||
import RouterService from '../../files/src/services/RouterService' |
|||
import router from './router' |
|||
|
|||
registerFileDropView() |
|||
registerPublicShareView() |
|||
registerPublicFileShareView() |
|||
|
|||
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' }) |
|||
|
|||
// Get the current view from state and set it active
|
|||
const view = loadState<string>('files_sharing', 'view') |
|||
const navigation = getNavigation() |
|||
navigation.setActive(navigation.views.find(({ id }) => id === view) ?? null) |
|||
|
|||
// Force our own router
|
|||
window.OCP.Files = window.OCP.Files ?? {} |
|||
window.OCP.Files.Router = new RouterService(router) |
|||
@ -0,0 +1,54 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
import type { RawLocation, Route } from 'vue-router' |
|||
import type { ErrorHandler } from 'vue-router/types/router.d.ts' |
|||
|
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import { generateUrl } from '@nextcloud/router' |
|||
import queryString from 'query-string' |
|||
import Router from 'vue-router' |
|||
import Vue from 'vue' |
|||
|
|||
const view = loadState<string>('files_sharing', 'view') |
|||
const sharingToken = loadState<string>('files_sharing', 'sharingToken') |
|||
|
|||
Vue.use(Router) |
|||
|
|||
// Prevent router from throwing errors when we're already on the page we're trying to go to
|
|||
const originalPush = Router.prototype.push as (to, onComplete?, onAbort?) => Promise<Route> |
|||
Router.prototype.push = function push(to: RawLocation, onComplete?: ((route: Route) => void) | undefined, onAbort?: ErrorHandler | undefined): Promise<Route> { |
|||
if (onComplete || onAbort) return originalPush.call(this, to, onComplete, onAbort) |
|||
return originalPush.call(this, to).catch(err => err) |
|||
} |
|||
|
|||
const router = new Router({ |
|||
mode: 'history', |
|||
|
|||
// if index.php is in the url AND we got this far, then it's working:
|
|||
// let's keep using index.php in the url
|
|||
base: generateUrl('/s'), |
|||
linkActiveClass: 'active', |
|||
|
|||
routes: [ |
|||
{ |
|||
path: '/', |
|||
// Pretending we're using the default view
|
|||
redirect: { name: 'filelist', params: { view, token: sharingToken } }, |
|||
}, |
|||
{ |
|||
path: '/:token', |
|||
name: 'filelist', |
|||
props: true, |
|||
}, |
|||
], |
|||
|
|||
// Custom stringifyQuery to prevent encoding of slashes in the url
|
|||
stringifyQuery(query) { |
|||
const result = queryString.stringify(query).replace(/%2F/gmi, '/') |
|||
return result ? ('?' + result) : '' |
|||
}, |
|||
}) |
|||
|
|||
export default router |
|||
@ -0,0 +1,67 @@ |
|||
<!-- |
|||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
- SPDX-License-Identifier: AGPL-3.0-or-later |
|||
--> |
|||
<template> |
|||
<NcEmptyContent class="file-drop-empty-content" |
|||
data-cy-files-sharing-file-drop |
|||
:name="t('files_sharing', 'File drop')"> |
|||
<template #icon> |
|||
<NcIconSvgWrapper :svg="svgCloudUpload" /> |
|||
</template> |
|||
<template #description> |
|||
{{ t('files_sharing', 'Upload files to {foldername}.', { foldername }) }} |
|||
{{ disclaimer === '' ? '' : t('files_sharing', 'By uploading files, you agree to the terms of service.') }} |
|||
</template> |
|||
<template #action> |
|||
<template v-if="disclaimer"> |
|||
<!-- Terms of service if enabled --> |
|||
<NcButton type="primary" @click="showDialog = true"> |
|||
{{ t('files_sharing', 'View terms of service') }} |
|||
</NcButton> |
|||
<NcDialog close-on-click-outside |
|||
content-classes="terms-of-service-dialog" |
|||
:open.sync="showDialog" |
|||
:name="t('files_sharing', 'Terms of service')" |
|||
:message="disclaimer" /> |
|||
</template> |
|||
<UploadPicker allow-folders |
|||
:content="() => []" |
|||
no-menu |
|||
:destination="uploadDestination" |
|||
multiple /> |
|||
</template> |
|||
</NcEmptyContent> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
import { getUploader, UploadPicker } from '@nextcloud/upload' |
|||
import { ref } from 'vue' |
|||
|
|||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' |
|||
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' |
|||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' |
|||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' |
|||
import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw' |
|||
|
|||
defineProps<{ |
|||
foldername: string |
|||
}>() |
|||
|
|||
const disclaimer = loadState<string>('files_sharing', 'disclaimer', '') |
|||
const showDialog = ref(false) |
|||
const uploadDestination = getUploader().destination |
|||
</script> |
|||
|
|||
<style scoped> |
|||
:deep(.terms-of-service-dialog) { |
|||
min-height: min(100px, 20vh); |
|||
} |
|||
/* TODO fix in library */ |
|||
.file-drop-empty-content :deep(.empty-content__action) { |
|||
display: flex; |
|||
gap: var(--default-grid-baseline); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,59 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
import type { VueConstructor } from 'vue' |
|||
|
|||
import { Folder, Permission, View, davRemoteURL, davRootPath, getNavigation } from '@nextcloud/files' |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw' |
|||
import Vue from 'vue' |
|||
|
|||
export default () => { |
|||
const foldername = loadState<string>('files_sharing', 'filename') |
|||
|
|||
let FilesViewFileDropEmptyContent: VueConstructor |
|||
let fileDropEmptyContentInstance: Vue |
|||
|
|||
const view = new View({ |
|||
id: 'public-file-drop', |
|||
name: t('files_sharing', 'File drop'), |
|||
caption: t('files_sharing', 'Upload files to {foldername}', { foldername }), |
|||
icon: svgCloudUpload, |
|||
order: 1, |
|||
|
|||
emptyView: async (div: HTMLDivElement) => { |
|||
if (FilesViewFileDropEmptyContent === undefined) { |
|||
const { default: component } = await import('../views/FilesViewFileDropEmptyContent.vue') |
|||
FilesViewFileDropEmptyContent = Vue.extend(component) |
|||
} |
|||
if (fileDropEmptyContentInstance) { |
|||
fileDropEmptyContentInstance.$destroy() |
|||
} |
|||
fileDropEmptyContentInstance = new FilesViewFileDropEmptyContent({ |
|||
propsData: { |
|||
foldername, |
|||
}, |
|||
}) |
|||
fileDropEmptyContentInstance.$mount(div) |
|||
}, |
|||
|
|||
getContents: async () => { |
|||
return { |
|||
contents: [], |
|||
// Fake a writeonly folder as root
|
|||
folder: new Folder({ |
|||
id: 0, |
|||
source: `${davRemoteURL}${davRootPath}`, |
|||
root: davRootPath, |
|||
owner: null, |
|||
permissions: Permission.CREATE, |
|||
}), |
|||
} |
|||
}, |
|||
}) |
|||
|
|||
const Navigation = getNavigation() |
|||
Navigation.register(view) |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
import type { FileStat, ResponseDataDetailed } from 'webdav' |
|||
import { Folder, Permission, View, davGetDefaultPropfind, davRemoteURL, davRootPath, getNavigation } from '@nextcloud/files' |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
import { CancelablePromise } from 'cancelable-promise' |
|||
import LinkSvg from '@mdi/svg/svg/link.svg?raw' |
|||
|
|||
import { resultToNode } from '../../../files/src/services/Files' |
|||
import { client } from '../../../files/src/services/WebdavClient' |
|||
import logger from '../services/logger' |
|||
|
|||
export default () => { |
|||
const view = new View({ |
|||
id: 'public-file-share', |
|||
name: t('files_sharing', 'Public file share'), |
|||
caption: t('files_sharing', 'Public shared file.'), |
|||
|
|||
emptyTitle: t('files_sharing', 'No file'), |
|||
emptyCaption: t('files_sharing', 'The file shared with you will show up here'), |
|||
|
|||
icon: LinkSvg, |
|||
order: 1, |
|||
|
|||
getContents: () => { |
|||
return new CancelablePromise(async (resolve, reject, onCancel) => { |
|||
const abort = new AbortController() |
|||
onCancel(() => abort.abort()) |
|||
try { |
|||
const node = await client.stat( |
|||
davRootPath, |
|||
{ |
|||
data: davGetDefaultPropfind(), |
|||
details: true, |
|||
signal: abort.signal, |
|||
}, |
|||
) as ResponseDataDetailed<FileStat> |
|||
|
|||
resolve({ |
|||
// We only have one file as the content
|
|||
contents: [resultToNode(node.data)], |
|||
// Fake a readonly folder as root
|
|||
folder: new Folder({ |
|||
id: 0, |
|||
source: `${davRemoteURL}${davRootPath}`, |
|||
root: davRootPath, |
|||
owner: null, |
|||
permissions: Permission.READ, |
|||
attributes: { |
|||
// Ensure the share note is set on the root
|
|||
note: node.data.props?.note, |
|||
}, |
|||
}), |
|||
}) |
|||
} catch (e) { |
|||
logger.error(e as Error) |
|||
reject(e as Error) |
|||
} |
|||
}) |
|||
}, |
|||
}) |
|||
|
|||
const Navigation = getNavigation() |
|||
Navigation.register(view) |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
import { View, getNavigation } from '@nextcloud/files' |
|||
import LinkSvg from '@mdi/svg/svg/link.svg?raw' |
|||
|
|||
import { getContents } from '../../../files/src/services/Files' |
|||
|
|||
export default () => { |
|||
const view = new View({ |
|||
id: 'public-share', |
|||
name: t('files_sharing', 'Public share'), |
|||
caption: t('files_sharing', 'Public shared files.'), |
|||
|
|||
emptyTitle: t('files_sharing', 'No files'), |
|||
emptyCaption: t('files_sharing', 'Files and folders shared with you will show up here'), |
|||
|
|||
icon: LinkSvg, |
|||
order: 1, |
|||
|
|||
getContents, |
|||
}) |
|||
|
|||
const Navigation = getNavigation() |
|||
Navigation.register(view) |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue