share: show directory structure

pull/880/head
Clément Saccoccio 2023-10-19 22:12:26 +03:00
parent 85a8f32da1
commit c85a611651
10 changed files with 51 additions and 34 deletions

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
function getWildcard($param) function getWildcard($param)
{ {
return [ return [
@ -34,7 +36,6 @@ return [
w(['name' => 'Page#tags', 'url' => '/tags/{name}', 'verb' => 'GET'], 'name'), w(['name' => 'Page#tags', 'url' => '/tags/{name}', 'verb' => 'GET'], 'name'),
// Public folder share // Public folder share
['name' => 'Public#showShare', 'url' => '/s/{token}', 'verb' => 'GET'],
[ [
'name' => 'Public#showAuthenticate', 'name' => 'Public#showAuthenticate',
'url' => '/s/{token}/authenticate/{redirect}', 'url' => '/s/{token}/authenticate/{redirect}',
@ -45,6 +46,7 @@ return [
'url' => '/s/{token}/authenticate/{redirect}', 'url' => '/s/{token}/authenticate/{redirect}',
'verb' => 'POST', 'verb' => 'POST',
], ],
w(['name' => 'Public#showShare', 'url' => '/s/{token}/{path}', 'verb' => 'GET'], 'path'),
// Public album share // Public album share
['name' => 'PublicAlbum#showShare', 'url' => '/a/{token}', 'verb' => 'GET'], ['name' => 'PublicAlbum#showShare', 'url' => '/a/{token}', 'verb' => 'GET'],

View File

@ -15,12 +15,15 @@ class FoldersController extends GenericApiController
{ {
/** /**
* @NoAdminRequired * @NoAdminRequired
*
* @PublicPage
*/ */
public function sub(string $folder): Http\Response public function sub(string $folder): Http\Response
{ {
return Util::guardEx(function () use ($folder) { return Util::guardEx(function () use ($folder) {
try { try {
$node = Util::getUserFolder()->get($folder); $rootNode = $this->fs->getShareNode() ?? Util::getUserFolder();
$node = $rootNode->get($folder);
} catch (\OCP\Files\NotFoundException) { } catch (\OCP\Files\NotFoundException) {
throw Exceptions::NotFound('Folder not found'); throw Exceptions::NotFound('Folder not found');
} }
@ -60,7 +63,6 @@ class FoldersController extends GenericApiController
return [ return [
'fileid' => $node->getId(), 'fileid' => $node->getId(),
'name' => $node->getName(), 'name' => $node->getName(),
'path' => $node->getPath(),
'previews' => $this->tq->getRootPreviews($root), 'previews' => $this->tq->getRootPreviews($root),
]; ];
}, $folders); }, $folders);

View File

@ -84,7 +84,16 @@ class FsManager
throw new \Exception('Share is not a folder'); throw new \Exception('Share is not a folder');
} }
if ($path = $this->getRequestFolder()) {
try {
$node = $share->get(Util::sanitizePath($path));
} catch (\OCP\Files\NotFoundException $e) {
throw new \Exception("Folder not found: {$e->getMessage()}");
}
$root->addFolder($node);
} else {
$root->addFolder($share); $root->addFolder($share);
}
return $root; return $root;
} }

View File

@ -580,7 +580,7 @@ export default defineComponent({
} }
// Folder // Folder
if (this.routeIsFolders) { if (this.routeIsFolders || this.routeIsFolderShare) {
const path = utils.getFolderRoutePath(this.config.folders_path); const path = utils.getFolderRoutePath(this.config.folders_path);
set(DaysFilterType.FOLDER, path); set(DaysFilterType.FOLDER, path);
if (this.$route.query.recursive) { if (this.$route.query.recursive) {

View File

@ -58,18 +58,17 @@ export default defineComponent({
computed: { computed: {
/** Open folder */ /** Open folder */
target() { target() {
const path = this.data.path let currentPath: string[] | string = this.$route.params.path || [];
.split('/') if (typeof currentPath === 'string') {
.filter((x) => x) currentPath = currentPath.split('/');
.slice(2) as string[];
// Remove base path if present
const basePath = this.config.folders_path.split('/').filter((x) => x);
if (path.length >= basePath.length && path.slice(0, basePath.length).every((x, i) => x === basePath[i])) {
path.splice(0, basePath.length);
} }
return { name: 'folders', params: { path } }; return {
name: this.$route.name,
params: {
path: [...currentPath, this.data.name],
},
};
}, },
}, },
@ -89,12 +88,6 @@ export default defineComponent({
// Reset state // Reset state
this.error = false; this.error = false;
// Check if valid path present
if (!this.data.path) {
this.error = true;
return;
}
// Get preview infos // Get preview infos
const previews = this.data.previews; const previews = this.data.previews;
if (previews) { if (previews) {

View File

@ -35,7 +35,7 @@ export default defineComponent({
}, },
currentmatter(): Component | null { currentmatter(): Component | null {
if (this.routeIsFolders) { if (this.routeIsFolders || this.routeIsFolderShare) {
return FolderDynamicTopMatter; return FolderDynamicTopMatter;
} else if (this.routeIsPlaces) { } else if (this.routeIsPlaces) {
return PlacesDynamicTopMatterVue; return PlacesDynamicTopMatterVue;
@ -53,8 +53,8 @@ export default defineComponent({
return this.$route.params.name || ''; return this.$route.params.name || '';
} }
// Show share name for public shares // Show share name for public shares, except for folder share, because the name is already present in the breadcrumbs
if (this.routeIsPublic) { if (this.routeIsPublic && !this.routeIsFolderShare) {
return PublicShareHeader.title; return PublicShareHeader.title;
} }

View File

@ -1,16 +1,17 @@
<template> <template>
<div class="top-matter"> <div class="top-matter">
<NcBreadcrumbs> <NcBreadcrumbs>
<NcBreadcrumb title="Home" :to="{ name: 'folders' }"> <NcBreadcrumb :title="rootFolderName" :to="{ name: $route.name }">
<template #icon> <template v-if="routeIsPublic" #icon>
<HomeIcon :size="20" /> <ShareIcon :size="20" />
<span class="share-name">{{ rootFolderName }}</span>
</template> </template>
</NcBreadcrumb> </NcBreadcrumb>
<NcBreadcrumb <NcBreadcrumb
v-for="folder in list" v-for="folder in list"
:key="folder.idx" :key="folder.idx"
:title="folder.text" :title="folder.text"
:to="{ name: 'folders', params: { path: folder.path } }" :to="{ name: $route.name, params: { path: folder.path } }"
/> />
</NcBreadcrumbs> </NcBreadcrumbs>
@ -23,7 +24,12 @@
<TimelineIcon v-else :size="20" /> <TimelineIcon v-else :size="20" />
</template> </template>
</NcActionButton> </NcActionButton>
<NcActionButton :aria-label="t('memories', 'Share folder')" @click="share()" close-after-click> <NcActionButton
v-if="!routeIsPublic"
:aria-label="t('memories', 'Share folder')"
@click="share()"
close-after-click
>
{{ t('memories', 'Share folder') }} {{ t('memories', 'Share folder') }}
<template #icon> <ShareIcon :size="20" /> </template> <template #icon> <ShareIcon :size="20" /> </template>
</NcActionButton> </NcActionButton>
@ -42,8 +48,8 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions';
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton'; import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton';
import * as utils from '../../services/utils'; import * as utils from '../../services/utils';
import * as PublicShareHeader from './PublicShareHeader';
import HomeIcon from 'vue-material-design-icons/Home.vue';
import ShareIcon from 'vue-material-design-icons/ShareVariant.vue'; import ShareIcon from 'vue-material-design-icons/ShareVariant.vue';
import TimelineIcon from 'vue-material-design-icons/ImageMultiple.vue'; import TimelineIcon from 'vue-material-design-icons/ImageMultiple.vue';
import FoldersIcon from 'vue-material-design-icons/FolderMultiple.vue'; import FoldersIcon from 'vue-material-design-icons/FolderMultiple.vue';
@ -56,7 +62,6 @@ export default defineComponent({
NcBreadcrumb, NcBreadcrumb,
NcActions, NcActions,
NcActionButton, NcActionButton,
HomeIcon,
ShareIcon, ShareIcon,
TimelineIcon, TimelineIcon,
FoldersIcon, FoldersIcon,
@ -86,6 +91,10 @@ export default defineComponent({
recursive(): boolean { recursive(): boolean {
return !!this.$route.query.recursive; return !!this.$route.query.recursive;
}, },
rootFolderName(): string {
return this.routeIsPublic ? PublicShareHeader.title : 'Home';
},
}, },
methods: { methods: {
@ -110,6 +119,9 @@ export default defineComponent({
.breadcrumb { .breadcrumb {
min-width: 0; min-width: 0;
height: unset; height: unset;
.share-name {
margin-left: 1em;
}
} }
} }
</style> </style>

View File

@ -45,6 +45,7 @@ export default defineComponent({
currentmatter() { currentmatter() {
switch (this.$route.name) { switch (this.$route.name) {
case _m.routes.Folders.name: case _m.routes.Folders.name:
case _m.routes.FolderShare.name:
return FolderTopMatter; return FolderTopMatter;
case _m.routes.Albums.name: case _m.routes.Albums.name:
return AlbumTopMatter; return AlbumTopMatter;

View File

@ -110,7 +110,7 @@ export const routes: { [key in RouteId]: RouteConfig } = {
}, },
FolderShare: { FolderShare: {
path: '/s/:token', path: '/s/:token/:path*',
component: Timeline, component: Timeline,
name: 'folder-share', name: 'folder-share',
props: (route: Route) => ({ rootTitle: t('memories', 'Shared Folder') }), props: (route: Route) => ({ rootTitle: t('memories', 'Shared Folder') }),

View File

@ -156,8 +156,6 @@ export interface IExif {
} }
export interface IFolder extends IPhoto { export interface IFolder extends IPhoto {
/** Path to folder */
path: string;
/** Photos for preview images */ /** Photos for preview images */
previews?: IPhoto[]; previews?: IPhoto[];
/** Name of folder */ /** Name of folder */