share: show directory structure
parent
85a8f32da1
commit
c85a611651
|
@ -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'],
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -84,7 +84,16 @@ class FsManager
|
||||||
throw new \Exception('Share is not a folder');
|
throw new \Exception('Share is not a folder');
|
||||||
}
|
}
|
||||||
|
|
||||||
$root->addFolder($share);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
return $root;
|
return $root;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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') }),
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
Loading…
Reference in New Issue