diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue index 26b26097532..c3aac0f1a23 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/RoleDropdown.vue @@ -118,6 +118,7 @@ import { import * as uuid from 'uuid' import { defineComponent } from '@vue/runtime-core' import { PropType } from '@vue/composition-api' +import { useIncomingParentShare } from '../../../../composables/parentShare' export default defineComponent({ name: 'RoleDropdown', @@ -147,6 +148,9 @@ export default defineComponent({ required: true } }, + setup() { + return { ...useIncomingParentShare() } + }, data() { return { selectedRole: null, @@ -181,10 +185,6 @@ export default defineComponent({ resourceIsSharable() { return this.allowSharePermission && this.resource.canShare() }, - share() { - // the root share has an empty key in the shares tree. That's the reason why we retrieve the share by an empty key here - return this.sharesTree['/']?.find((s) => s.incoming) - }, allowCustomSharing() { return this.capabilities?.files_sharing?.allow_custom }, @@ -193,9 +193,9 @@ export default defineComponent({ return SpacePeopleShareRoles.list() } - if (this.share?.incoming && this.resourceIsSharable) { + if (this.incomingParentShare && this.resourceIsSharable) { return PeopleShareRoles.filterByBitmask( - parseInt(this.share.permissions), + parseInt(this.incomingParentShare.permissions), this.resource.isFolder, this.allowSharePermission, this.allowCustomSharing !== false @@ -205,8 +205,8 @@ export default defineComponent({ return PeopleShareRoles.list(this.resource.isFolder, this.allowCustomSharing !== false) }, availablePermissions() { - if (this.share?.incoming && this.resourceIsSharable) { - return SharePermissions.bitmaskToPermissions(parseInt(this.share.permissions)) + if (this.incomingParentShare && this.resourceIsSharable) { + return SharePermissions.bitmaskToPermissions(parseInt(this.incomingParentShare.permissions)) } return this.customPermissionsRole.permissions(this.allowSharePermission) }, @@ -223,7 +223,8 @@ export default defineComponent({ window.removeEventListener('keydown', this.cycleRoles) }, - mounted() { + async mounted() { + await this.loadIncomingParentShare.perform(this.resource) this.applyRoleAndPermissions() window.addEventListener('keydown', this.cycleRoles) }, diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue index dd2ed57ef7e..04b05e39b91 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue @@ -129,6 +129,7 @@ import { useGraphClient } from 'web-client/src/composables' import CreateQuickLink from './Links/CreateQuickLink.vue' import { isLocationSpacesActive } from '../../../router' import { getLocaleFromLanguage } from 'web-pkg/src/helpers' +import { useIncomingParentShare } from '../../../composables/parentShare' export default defineComponent({ name: 'FileLinks', @@ -146,6 +147,7 @@ export default defineComponent({ return { ...useGraphClient(), + ...useIncomingParentShare(), hasSpaces: useCapabilitySpacesEnabled(), hasShareJail: useCapabilityShareJailEnabled(), hasResharing: useCapabilityFilesSharingResharing(), @@ -182,11 +184,6 @@ export default defineComponent({ return this.currentFileOutgoingLinks.find((link) => link.quicklink === true) }, - share() { - // the root share has an empty key in the shares tree. That's the reason why we retrieve the share by an empty key here - return this.sharesTree['/']?.find((s) => s.incoming) - }, - expirationDate() { const expireDate = this.capabilities.files_sharing.public.expire_date @@ -218,9 +215,9 @@ export default defineComponent({ }, availableRoleOptions() { - if (this.share?.incoming && this.canCreatePublicLinks) { + if (this.incomingParentShare && this.canCreatePublicLinks) { return LinkShareRoles.filterByBitmask( - parseInt(this.share.permissions), + parseInt(this.incomingParentShare.permissions), this.highlightedFile.isFolder, this.hasPublicLinkEditing, this.hasPublicLinkAliasSupport @@ -354,6 +351,9 @@ export default defineComponent({ return this.$route.params.storageId || null } }, + async mounted() { + await this.loadIncomingParentShare.perform(this.highlightedFile) + }, methods: { ...mapActions('Files', ['addLink', 'updateLink', 'removeLink']), ...mapActions(['showMessage', 'createModal', 'hideModal']), diff --git a/packages/web-app-files/src/composables/parentShare/index.ts b/packages/web-app-files/src/composables/parentShare/index.ts new file mode 100644 index 00000000000..9912152fdb3 --- /dev/null +++ b/packages/web-app-files/src/composables/parentShare/index.ts @@ -0,0 +1 @@ +export * from './useIncomingParentShare' diff --git a/packages/web-app-files/src/composables/parentShare/useIncomingParentShare.ts b/packages/web-app-files/src/composables/parentShare/useIncomingParentShare.ts new file mode 100644 index 00000000000..5c09b9fe658 --- /dev/null +++ b/packages/web-app-files/src/composables/parentShare/useIncomingParentShare.ts @@ -0,0 +1,47 @@ +import { buildShare } from '../../helpers/resources' +import { useStore } from 'web-pkg/src/composables' +import { useActiveLocation } from '../router' +import { isLocationSharesActive } from '../../router' +import { computed, ref, unref } from '@vue/composition-api' +import { useTask } from 'vue-concurrency' +import { clientService } from 'web-pkg/src/services' + +export function useIncomingParentShare() { + const store = useStore() + const incomingParentShare = ref(null) + const sharesTree = computed(() => store.state.Files.sharesTree) + const isSharedWithMeLocation = useActiveLocation(isLocationSharesActive, 'files-shares-with-me') + + const loadIncomingParentShare = useTask(function* (signal, resource) { + console.log('resource', resource) + let parentShare + for (const shares of Object.values(unref(sharesTree)) as any) { + parentShare = shares.find((s) => s.incoming) + if (parentShare) { + console.log('via incoming:', parentShare) + incomingParentShare.value = parentShare + return + } + } + + if (unref(isSharedWithMeLocation)) { + incomingParentShare.value = resource.share + console.log('via share obj', incomingParentShare.value) + return + } + + if (resource.shareId) { + const parentShare = yield clientService.owncloudSdk.shares.getShare(resource.shareId) + if (parentShare) { + incomingParentShare.value = buildShare(parentShare.shareInfo, resource, true) + console.log('via share id', incomingParentShare.value) + return + } + } + + console.log('No parent share') + incomingParentShare.value = null + }) + + return { loadIncomingParentShare, incomingParentShare } +} diff --git a/packages/web-app-files/src/helpers/resources.ts b/packages/web-app-files/src/helpers/resources.ts index d4837bdc844..5d7e07181de 100644 --- a/packages/web-app-files/src/helpers/resources.ts +++ b/packages/web-app-files/src/helpers/resources.ts @@ -70,6 +70,7 @@ export function buildResource(resource): Resource { permissions: (resource.fileInfo[DavProperty.Permissions] as string) || '', starred: resource.fileInfo[DavProperty.IsFavorite] !== '0', etag: resource.fileInfo[DavProperty.ETag], + shareId: resource.fileInfo[DavProperty.ShareId], sharePermissions: resource.fileInfo[DavProperty.SharePermissions], shareTypes: (function () { if (resource.fileInfo[DavProperty.ShareTypes]) { diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Collaborators/RoleDropdown.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/Collaborators/RoleDropdown.spec.js index 3fea667faba..f170cfe346e 100644 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/Collaborators/RoleDropdown.spec.js +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Collaborators/RoleDropdown.spec.js @@ -274,7 +274,13 @@ function getMountOptions({ }, store: getStore(sharesTree), localVue, - stubs + stubs, + mocks: { + loadIncomingParentShare: { + perform: jest.fn() + }, + incomingParentShare: null + } } } diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js index 24ead3fa009..ebce64d2f53 100644 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/FileLinks.spec.js @@ -212,6 +212,10 @@ describe('FileLinks', () => { ...stubs }, mocks: { + loadIncomingParentShare: { + perform: jest.fn() + }, + incomingParentShare: null, $route: { params: {} }, @@ -230,6 +234,10 @@ describe('FileLinks', () => { localVue, store: store, mocks: { + loadIncomingParentShare: { + perform: jest.fn() + }, + incomingParentShare: null, $route: { params: {} }, diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/FileShares.spec.js b/packages/web-app-files/tests/unit/components/SideBar/Shares/FileShares.spec.js index fb8c587ae79..328ede5280a 100644 --- a/packages/web-app-files/tests/unit/components/SideBar/Shares/FileShares.spec.js +++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/FileShares.spec.js @@ -10,6 +10,7 @@ import Collaborators from '@/__fixtures__/collaborators' import { spaceRoleManager } from 'web-client/src/helpers/share' import * as reactivityComposables from 'web-pkg/src/composables/reactivity' import * as routerComposables from 'web-pkg/src/composables/router' +import { useActiveLocation } from '@files/src/composables/router' import FileShares from '@files/src/components/SideBar/Shares/FileShares.vue' import { buildSpace } from 'web-client/src/helpers' import { clientService } from 'web-pkg/src/services' @@ -26,6 +27,7 @@ localVue.use(GetTextPlugin, { jest.mock('web-pkg/src/composables/reactivity') jest.mock('web-pkg/src/composables/router') +jest.mock('@files/src/composables/router') const user = Users.alice const collaborators = [Collaborators[0], Collaborators[1]] @@ -275,6 +277,7 @@ const storeOptions = (data) => { function getMountedWrapper(data) { routerComposables.useRouteParam.mockReturnValue(() => storageId) + useActiveLocation.mockReturnValue(() => false) return mount(FileShares, { localVue, @@ -302,6 +305,7 @@ function getMountedWrapper(data) { function getShallowMountedWrapper(data, loading = false) { reactivityComposables.useDebouncedRef.mockImplementationOnce(() => loading) routerComposables.useRouteParam.mockReturnValue(() => storageId) + useActiveLocation.mockReturnValue(() => false) return shallowMount(FileShares, { localVue, diff --git a/packages/web-app-files/tests/unit/helpers/path.spec.js b/packages/web-app-files/tests/unit/helpers/path.spec.js index 0c2d9b88e4d..cc3f000bbbd 100644 --- a/packages/web-app-files/tests/unit/helpers/path.spec.js +++ b/packages/web-app-files/tests/unit/helpers/path.spec.js @@ -13,7 +13,7 @@ describe('build an array of parent paths from a provided path', () => { it('should prepend resulting paths with a "/" if none was given', () => { const paths = getParentPaths('a/b/c', false) - expect(paths).toEqual(['/a/b', '/a', '']) + expect(paths).toEqual(['/a/b', '/a']) }) it('should make no difference between "a/b/c" and "/a/b/c" with includeCurrent=false', () => { @@ -36,11 +36,11 @@ describe('build an array of parent paths from a provided path', () => { it('should not interpret a trailing slash as yet another path segment', () => { const paths = getParentPaths('/a/b/c/', true) - expect(paths).toEqual(['/a/b/c', '/a/b', '/a', '']) + expect(paths).toEqual(['/a/b/c', '/a/b', '/a']) }) it('should include the provided path in the result if includeCurrent=true', () => { const paths = getParentPaths('a/b/c', true) - expect(paths).toEqual(['/a/b/c', '/a/b', '/a', '']) + expect(paths).toEqual(['/a/b/c', '/a/b', '/a']) }) }) diff --git a/packages/web-client/src/helpers/resource/types.ts b/packages/web-client/src/helpers/resource/types.ts index e11b4499dc6..c77b588178b 100644 --- a/packages/web-client/src/helpers/resource/types.ts +++ b/packages/web-client/src/helpers/resource/types.ts @@ -22,6 +22,7 @@ export interface Resource { starred?: boolean etag?: string sharePermissions?: number + shareId?: string shareTypes?: number[] privateLink?: string diff --git a/packages/web-pkg/src/constants/dav.ts b/packages/web-pkg/src/constants/dav.ts index ee675c638af..5e20823b476 100644 --- a/packages/web-pkg/src/constants/dav.ts +++ b/packages/web-pkg/src/constants/dav.ts @@ -26,6 +26,7 @@ export abstract class DavProperty { static readonly ResourceType: string = '{DAV:}resourcetype' static readonly DownloadURL: string = '{http://owncloud.org/ns}downloadURL' + static readonly ShareId: string = '{http://owncloud.org/ns}shareid' static readonly ShareTypes: string = '{http://owncloud.org/ns}share-types' static readonly SharePermissions: string = '{http://open-collaboration-services.org/ns}share-permissions' @@ -53,6 +54,7 @@ export abstract class DavProperties { DavProperty.Name, DavProperty.OwnerId, DavProperty.OwnerDisplayName, + DavProperty.ShareId, DavProperty.ShareTypes, DavProperty.PrivateLink, DavProperty.ContentLength,