feat: New create link share button

Signed-off-by: nfebe <fenn25.fn@gmail.com>
This commit is contained in:
nfebe 2025-02-14 22:56:44 +01:00
parent d8dc0611aa
commit 16b98e7493
5 changed files with 549 additions and 209 deletions

View file

@ -0,0 +1,235 @@
<!--
- SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcActions class="sharing-entry__actions"
:aria-label="actionsTooltip"
menu-align="right"
:open="localOpen"
@close="onCancel">
<!-- pending data menu -->
<NcActionText v-if="errors.pending"
class="error">
<template #icon>
<ErrorIcon :size="20" />
</template>
{{ errors.pending }}
</NcActionText>
<NcActionText v-else icon="icon-info">
{{ t('files_sharing', 'Please enter the following required information before creating the share') }}
</NcActionText>
<!-- password -->
<NcActionCheckbox v-if="pendingPassword"
:checked="localIsPasswordProtected"
:disabled="config.enforcePasswordForPublicLink || saving"
class="share-link-password-checkbox"
@update:checked="onPasswordProtectedChange">
{{ config.enforcePasswordForPublicLink ? t('files_sharing', 'Password protection (enforced)') : t('files_sharing', 'Password protection') }}
</NcActionCheckbox>
<NcActionInput v-if="pendingEnforcedPassword || share.password"
class="share-link-password"
:label="t('files_sharing', 'Enter a password')"
:value="share.password"
:disabled="saving"
:required="config.enableLinkPasswordByDefault || config.enforcePasswordForPublicLink"
:minlength="isPasswordPolicyEnabled && config.passwordPolicy.minLength"
autocomplete="new-password"
@submit="onNewLinkShare(true)">
<template #icon>
<LockIcon :size="20" />
</template>
</NcActionInput>
<NcActionCheckbox v-if="pendingDefaultExpirationDate"
:checked="localDefaultExpirationDateEnabled"
:disabled="pendingEnforcedExpirationDate || saving"
class="share-link-expiration-date-checkbox"
@update:checked="onExpirationDateToggleChange">
{{ config.isDefaultExpireDateEnforced ? t('files_sharing', 'Enable link expiration (enforced)') : t('files_sharing', 'Enable link expiration') }}
</NcActionCheckbox>
<!-- expiration date -->
<NcActionInput v-if="(pendingDefaultExpirationDate || pendingEnforcedExpirationDate) && localDefaultExpirationDateEnabled"
data-cy-files-sharing-expiration-date-input
class="share-link-expire-date"
:label="pendingEnforcedExpirationDate ? t('files_sharing', 'Enter expiration date (enforced)') : t('files_sharing', 'Enter expiration date')"
:disabled="saving"
:is-native-picker="true"
:hide-label="true"
:value="new Date(share.expireDate)"
type="date"
:min="dateTomorrow"
:max="maxExpirationDateEnforced"
@change="expirationDateChanged($event)">
<template #icon>
<IconCalendarBlank :size="20" />
</template>
</NcActionInput>
<NcActionButton @click.prevent.stop="onNewLinkShare(true)">
<template #icon>
<CheckIcon :size="20" />
</template>
{{ t('files_sharing', 'Create share') }}
</NcActionButton>
<NcActionButton @click.prevent.stop="onCancel">
<template #icon>
<CloseIcon :size="20" />
</template>
{{ t('files_sharing', 'Cancel') }}
</NcActionButton>
</NcActions>
</template>
<script>
import { NcActions, NcActionButton, NcActionCheckbox, NcActionInput, NcActionText } from '@nextcloud/vue'
import ErrorIcon from 'vue-material-design-icons/Exclamation.vue'
import LockIcon from 'vue-material-design-icons/Lock.vue'
import CheckIcon from 'vue-material-design-icons/CheckBold.vue'
import CloseIcon from 'vue-material-design-icons/Close.vue'
import IconCalendarBlank from 'vue-material-design-icons/CalendarBlank.vue'
export default {
name: 'PendingActions',
components: {
NcActions,
NcActionButton,
NcActionCheckbox,
NcActionInput,
NcActionText,
ErrorIcon,
LockIcon,
CheckIcon,
CloseIcon,
IconCalendarBlank,
},
props: {
open: {
type: Boolean,
required: true,
},
share: {
type: Object,
required: true,
},
config: {
type: Object,
required: true,
},
errors: {
type: Object,
required: true,
},
pendingPassword: {
type: Boolean,
required: true,
},
pendingEnforcedPassword: {
type: Boolean,
required: true,
},
pendingDefaultExpirationDate: {
type: Boolean,
required: true,
},
pendingEnforcedExpirationDate: {
type: Boolean,
required: true,
},
defaultExpirationDateEnabled: {
type: Boolean,
required: true,
},
saving: {
type: Boolean,
required: true,
},
isPasswordPolicyEnabled: {
type: Boolean,
required: true,
},
dateTomorrow: {
type: Date,
required: true,
},
maxExpirationDateEnforced: {
type: String,
default: '',
},
actionsTooltip: {
type: String,
required: true,
},
isPasswordProtected: {
type: Boolean,
required: true,
},
},
data() {
return {
// Local state for checkboxes
localIsPasswordProtected: this.isPasswordProtected,
localDefaultExpirationDateEnabled: this.defaultExpirationDateEnabled,
// Local copy of the open prop
localOpen: this.open,
}
},
watch: {
// Sync changes from parent to local state
isPasswordProtected(newVal) {
this.localIsPasswordProtected = newVal
},
defaultExpirationDateEnabled(newVal) {
this.localDefaultExpirationDateEnabled = newVal
},
open(newVal) {
this.localOpen = newVal
},
// Sync changes from localOpen to parent
localOpen(newVal) {
this.$emit('update:open', newVal)
},
},
methods: {
onNewLinkShare(shareReviewComplete) {
this.$emit('new-link-share', shareReviewComplete)
},
onCancel() {
this.$emit('cancel')
},
onPasswordDisable() {
this.$emit('password-disable')
},
onExpirationDateToggleChange(enabled) {
this.localDefaultExpirationDateEnabled = enabled
this.$emit('update:defaultExpirationDateEnabled', enabled)
},
onPasswordProtectedChange(enabled) {
this.localIsPasswordProtected = enabled
this.$emit('update:isPasswordProtected', enabled)
},
expirationDateChanged(event) {
this.$emit('expiration-date-changed', event)
},
},
}
</script>
<style lang="scss" scoped>
.sharing-entry__actions {
.action-item {
~.action-item {
margin-inline-start: 0
}
}
}
</style>

View file

@ -9,7 +9,6 @@
<NcAvatar :is-no-user="true"
:icon-class="isEmailShareType ? 'avatar-link-share icon-mail-white' : 'avatar-link-share icon-public-white'"
class="sharing-entry__avatar" />
<div class="sharing-entry__summary">
<div class="sharing-entry__desc">
<span class="sharing-entry__title" :title="title">
@ -47,87 +46,28 @@
</div>
<!-- pending actions -->
<NcActions v-if="!pending && pendingDataIsMissing"
class="sharing-entry__actions"
:aria-label="actionsTooltip"
menu-align="right"
<PendingActions v-if="!pending && pendingDataIsMissing"
:open.sync="open"
@close="onCancel">
<!-- pending data menu -->
<NcActionText v-if="errors.pending"
class="error">
<template #icon>
<ErrorIcon :size="20" />
</template>
{{ errors.pending }}
</NcActionText>
<NcActionText v-else icon="icon-info">
{{ t('files_sharing', 'Please enter the following required information before creating the share') }}
</NcActionText>
<!-- password -->
<NcActionCheckbox v-if="pendingPassword"
:checked.sync="isPasswordProtected"
:disabled="config.enforcePasswordForPublicLink || saving"
class="share-link-password-checkbox"
@uncheck="onPasswordDisable">
{{ config.enforcePasswordForPublicLink ? t('files_sharing', 'Password protection (enforced)') : t('files_sharing', 'Password protection') }}
</NcActionCheckbox>
<NcActionInput v-if="pendingEnforcedPassword || share.password"
class="share-link-password"
:label="t('files_sharing', 'Enter a password')"
:value.sync="share.password"
:disabled="saving"
:required="config.enableLinkPasswordByDefault || config.enforcePasswordForPublicLink"
:minlength="isPasswordPolicyEnabled && config.passwordPolicy.minLength"
autocomplete="new-password"
@submit="onNewLinkShare(true)">
<template #icon>
<LockIcon :size="20" />
</template>
</NcActionInput>
<NcActionCheckbox v-if="pendingDefaultExpirationDate"
:checked.sync="defaultExpirationDateEnabled"
:disabled="pendingEnforcedExpirationDate || saving"
class="share-link-expiration-date-checkbox"
@update:model-value="onExpirationDateToggleUpdate">
{{ config.isDefaultExpireDateEnforced ? t('files_sharing', 'Enable link expiration (enforced)') : t('files_sharing', 'Enable link expiration') }}
</NcActionCheckbox>
<!-- expiration date -->
<NcActionInput v-if="(pendingDefaultExpirationDate || pendingEnforcedExpirationDate) && defaultExpirationDateEnabled"
data-cy-files-sharing-expiration-date-input
class="share-link-expire-date"
:label="pendingEnforcedExpirationDate ? t('files_sharing', 'Enter expiration date (enforced)') : t('files_sharing', 'Enter expiration date')"
:disabled="saving"
:is-native-picker="true"
:hide-label="true"
:value="new Date(share.expireDate)"
type="date"
:min="dateTomorrow"
:max="maxExpirationDateEnforced"
@update:model-value="onExpirationChange"
@change="expirationDateChanged">
<template #icon>
<IconCalendarBlank :size="20" />
</template>
</NcActionInput>
<NcActionButton @click.prevent.stop="onNewLinkShare(true)">
<template #icon>
<CheckIcon :size="20" />
</template>
{{ t('files_sharing', 'Create share') }}
</NcActionButton>
<NcActionButton @click.prevent.stop="onCancel">
<template #icon>
<CloseIcon :size="20" />
</template>
{{ t('files_sharing', 'Cancel') }}
</NcActionButton>
</NcActions>
:share="share"
:config="config"
:errors="errors"
:pending-password="pendingPassword"
:pending-enforced-password="pendingEnforcedPassword"
:pending-default-expiration-date="pendingDefaultExpirationDate"
:pending-enforced-expiration-date="pendingEnforcedExpirationDate"
:default-expiration-date-enabled="defaultExpirationDateEnabled"
:saving="saving"
:is-password-policy-enabled="isPasswordPolicyEnabled"
:date-tomorrow="dateTomorrow"
:max-expiration-date-enforced="maxExpirationDateEnforced"
:actions-tooltip="actionsTooltip"
:is-password-protected="isPasswordProtected"
@new-link-share="onNewLinkShare"
@cancel="onCancel"
@password-disable="onPasswordDisable"
@update:isPasswordProtected="onPasswordProtectedChange"
@update:defaultExpirationDateEnabled="onExpirationDateToggleChange"
@expiration-date-changed="expirationDateChanged" />
<!-- actions -->
<NcActions v-else-if="!loading"
@ -193,14 +133,6 @@
{{ t('files_sharing', 'Unshare') }}
</NcActionButton>
</template>
<!-- Create new share -->
<NcActionButton v-else-if="canReshare"
class="new-share-link"
:title="t('files_sharing', 'Create a new share link')"
:aria-label="t('files_sharing', 'Create a new share link')"
:icon="loading ? 'icon-loading-small' : 'icon-add'"
@click.prevent.stop="onNewLinkShare" />
</NcActions>
<!-- loading indicator to replace the menu -->
@ -223,29 +155,22 @@
</template>
<script>
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import { generateUrl, getBaseUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { ShareType } from '@nextcloud/sharing'
import VueQrcode from '@chenfengyuan/vue-qrcode'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionCheckbox from '@nextcloud/vue/components/NcActionCheckbox'
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
import NcActionLink from '@nextcloud/vue/components/NcActionLink'
import NcActionText from '@nextcloud/vue/components/NcActionText'
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
import NcActions from '@nextcloud/vue/components/NcActions'
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import moment from '@nextcloud/moment'
import Vue from 'vue'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
import Tune from 'vue-material-design-icons/Tune.vue'
import IconCalendarBlank from 'vue-material-design-icons/CalendarBlank.vue'
import IconQr from 'vue-material-design-icons/Qrcode.vue'
import ErrorIcon from 'vue-material-design-icons/Exclamation.vue'
import LockIcon from 'vue-material-design-icons/Lock.vue'
import CheckIcon from 'vue-material-design-icons/CheckBold.vue'
import ClipboardIcon from 'vue-material-design-icons/ContentCopy.vue'
import CloseIcon from 'vue-material-design-icons/Close.vue'
@ -256,10 +181,11 @@ import ShareExpiryTime from './ShareExpiryTime.vue'
import ExternalShareAction from './ExternalShareAction.vue'
import GeneratePassword from '../utils/GeneratePassword.ts'
import PendingActions from './PendingActions.vue'
import Share from '../models/Share.ts'
import SharesMixin from '../mixins/SharesMixin.js'
import ShareDetails from '../mixins/ShareDetails.js'
import logger from '../services/logger.ts'
import logger from '../logger.ts'
export default {
name: 'SharingEntryLink',
@ -268,23 +194,18 @@ export default {
ExternalShareAction,
NcActions,
NcActionButton,
NcActionCheckbox,
NcActionInput,
NcActionLink,
NcActionText,
NcActionSeparator,
NcAvatar,
NcDialog,
VueQrcode,
Tune,
IconCalendarBlank,
IconQr,
ErrorIcon,
LockIcon,
CheckIcon,
ClipboardIcon,
CloseIcon,
PlusIcon,
PendingActions,
SharingEntryQuickShareSelect,
ShareExpiryTime,
},
@ -314,6 +235,7 @@ export default {
ExternalLegacyLinkActions: OCA.Sharing.ExternalLinkActions.state,
ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
logger,
// tracks whether modal should be opened or not
showQRCode: false,
@ -686,95 +608,6 @@ export default {
this.shareCreationComplete = true
}
},
/**
* Push a new link share to the server
* And update or append to the list
* accordingly
*
* @param {Share} share the new share
* @param {boolean} [update] do we update the current share ?
*/
async pushNewLinkShare(share, update) {
try {
// do nothing if we're already pending creation
if (this.loading) {
return true
}
this.loading = true
this.errors = {}
const path = (this.fileInfo.path + '/' + this.fileInfo.name).replace('//', '/')
const options = {
path,
shareType: ShareType.Link,
password: share.password,
expireDate: share.expireDate ?? '',
attributes: JSON.stringify(this.fileInfo.shareAttributes),
// we do not allow setting the publicUpload
// before the share creation.
// Todo: We also need to fix the createShare method in
// lib/Controller/ShareAPIController.php to allow file requests
// (currently not supported on create, only update)
}
console.debug('Creating link share with options', options)
const newShare = await this.createShare(options)
this.open = false
this.shareCreationComplete = true
console.debug('Link share created', newShare)
// if share already exists, copy link directly on next tick
let component
if (update) {
component = await new Promise(resolve => {
this.$emit('update:share', newShare, resolve)
})
} else {
// adding new share to the array and copying link to clipboard
// using promise so that we can copy link in the same click function
// and avoid firefox copy permissions issue
component = await new Promise(resolve => {
this.$emit('add:share', newShare, resolve)
})
}
await this.getNode()
emit('files:node:updated', this.node)
// Execute the copy link method
// freshly created share component
// ! somehow does not works on firefox !
if (!this.config.enforcePasswordForPublicLink) {
// Only copy the link when the password was not forced,
// otherwise the user needs to copy/paste the password before finishing the share.
component.copyLink()
}
showSuccess(t('files_sharing', 'Link share created'))
} catch (data) {
const message = data?.response?.data?.ocs?.meta?.message
if (!message) {
showError(t('files_sharing', 'Error while creating the share'))
console.error(data)
return
}
if (message.match(/password/i)) {
this.onSyncError('password', message)
} else if (message.match(/date/i)) {
this.onSyncError('expireDate', message)
} else {
this.onSyncError('pending', message)
}
throw data
} finally {
this.loading = false
this.shareCreationComplete = true
}
},
async copyLink() {
try {
await navigator.clipboard.writeText(this.shareLink)
@ -866,11 +699,10 @@ export default {
this.onPasswordSubmit()
this.onNoteSubmit()
},
/**
* @param enabled True if expiration is enabled
*/
onExpirationDateToggleUpdate(enabled) {
onPasswordProtectedChange(enabled) {
this.isPasswordProtected = enabled
},
onExpirationDateToggleChange(enabled) {
this.share.expireDate = enabled ? this.formatDateToString(this.config.defaultExpirationDate) : ''
},

View file

@ -0,0 +1,11 @@
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getLoggerBuilder } from '@nextcloud/logger'
export default getLoggerBuilder()
.setApp('files_sharing')
.detectUser()
.build()

View file

@ -0,0 +1,239 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { emit } from '@nextcloud/event-bus'
import { ShareType } from '@nextcloud/sharing'
import GeneratePassword from '../utils/GeneratePassword'
import Share from '../models/Share'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import SharesMixin from '../mixins/SharesMixin.js'
export default {
computed: {
/**
* Whether the share policy has enforced properties.
* @return {boolean}
*/
sharePolicyHasEnforcedProperties() {
return this.config.enforcePasswordForPublicLink || this.config.isDefaultExpireDateEnforced
},
/**
* Whether required properties are missing.
* @return {boolean}
*/
enforcedPropertiesMissing() {
if (!this.sharePolicyHasEnforcedProperties) return false
if (!this.share) return true
if (this.share.id) return true
const isPasswordMissing = this.config.enforcePasswordForPublicLink && !this.share.password
const isExpireDateMissing = this.config.isDefaultExpireDateEnforced && !this.share.expireDate
return isPasswordMissing || isExpireDateMissing
},
},
mixins: [SharesMixin],
methods: {
/**
* Check if the share requires review
*
* @param {boolean} shareReviewComplete if the share was reviewed
* @return {boolean}
*/
shareRequiresReview(shareReviewComplete) {
// If a user clicks 'Create share' it means they have reviewed the share
if (shareReviewComplete) {
return false
}
return this.defaultExpirationDateEnabled || this.config.enableLinkPasswordByDefault
},
/**
* Handle the creation of a new link share.
* @param {boolean} shareReviewComplete Whether the share has been reviewed.
*/
async onNewLinkShare(shareReviewComplete = false) {
this.logger.debug('onNewLinkShare called (with this.share)', this.share)
if (this.loading) return
const shareDefaults = {
share_type: ShareType.Link,
}
if (this.config.isDefaultExpireDateEnforced) {
shareDefaults.expiration = this.formatDateToString(this.config.defaultExpirationDate)
}
if (
(this.sharePolicyHasEnforcedProperties && this.enforcedPropertiesMissing)
|| this.shareRequiresReview(shareReviewComplete)
) {
this.pending = true
this.shareCreationComplete = false
if (this.config.enableLinkPasswordByDefault || this.config.enforcePasswordForPublicLink) {
shareDefaults.password = await GeneratePassword(true)
}
const share = new Share(shareDefaults)
const component = await new Promise((resolve) => {
this.$emit('add:share', share, resolve)
})
this.open = false
this.pending = false
component.open = true
} else {
if (this.share && !this.share.id) {
if (this.checkShare(this.share)) {
try {
await this.pushNewLinkShare(this.share, true)
this.shareCreationComplete = true
} catch (e) {
this.pending = false
this.logger.error('Error creating share', e)
return
}
return
} else {
this.open = true
showError(t('files_sharing', 'Error, please enter proper password and/or expiration date'))
return
}
}
const share = new Share(shareDefaults)
await this.pushNewLinkShare(share)
this.shareCreationComplete = true
}
},
/**
* Push a new link share to the server
* And update or append to the list
* accordingly
*
* @param {Share} share the new share
* @param {boolean} [update] do we update the current share ?
*/
async pushNewLinkShare(share, update) {
try {
// do nothing if we're already pending creation
if (this.loading) {
return true
}
this.loading = true
this.errors = {}
const path = (this.fileInfo.path + '/' + this.fileInfo.name).replace('//', '/')
const options = {
path,
shareType: ShareType.Link,
password: share.password,
expireDate: share.expireDate ?? '',
attributes: JSON.stringify(this.fileInfo.shareAttributes),
// we do not allow setting the publicUpload
// before the share creation.
// Todo: We also need to fix the createShare method in
// lib/Controller/ShareAPIController.php to allow file requests
// (currently not supported on create, only update)
}
console.debug('Creating link share with options', options)
const newShare = await this.createShare(options)
this.open = false
this.shareCreationComplete = true
console.debug('Link share created', newShare)
// if share already exists, copy link directly on next tick
let component
if (update) {
component = await new Promise(resolve => {
this.$emit('update:share', newShare, resolve)
})
} else {
// adding new share to the array and copying link to clipboard
// using promise so that we can copy link in the same click function
// and avoid firefox copy permissions issue
component = await new Promise(resolve => {
this.$emit('add:share', newShare, resolve)
})
}
await this.getNode()
emit('files:node:updated', this.node)
// Execute the copy link method
// freshly created share component
// ! somehow does not works on firefox !
if (!this.config.enforcePasswordForPublicLink) {
// Only copy the link when the password was not forced,
// otherwise the user needs to copy/paste the password before finishing the share.
component.copyLink()
}
showSuccess(t('files_sharing', 'Link share created'))
} catch (data) {
const message = data?.response?.data?.ocs?.meta?.message
if (!message) {
showError(t('files_sharing', 'Error while creating the share'))
console.error(data)
return
}
if (message.match(/password/i)) {
this.onSyncError('password', message)
} else if (message.match(/date/i)) {
this.onSyncError('expireDate', message)
} else {
this.onSyncError('pending', message)
}
throw data
} finally {
this.loading = false
this.shareCreationComplete = true
}
},
/**
* Handle password protection change.
* @param {boolean} enabled Whether password protection is enabled.
*/
onPasswordProtectedChange(enabled) {
this.isPasswordProtected = enabled
},
/**
* Handle expiration date toggle change.
* @param {boolean} enabled Whether the expiration date is enabled.
*/
onExpirationDateToggleChange(enabled) {
this.share.expireDate = enabled ? this.formatDateToString(this.config.defaultExpirationDate) : ''
},
/**
* Handle expiration date change.
* @param {Event} event The change event.
*/
expirationDateChanged(event) {
const date = event.target.value
this.onExpirationChange(date)
this.defaultExpirationDateEnabled = !!date
},
/**
* Handle cancel action.
*/
onCancel() {
if (!this.shareCreationComplete) {
this.$emit('remove:share', this.share)
}
},
},
}

View file

@ -106,6 +106,17 @@
:file-info="fileInfo"
:shares="linkShares"
@open-sharing-details="toggleShareDetailsView" />
<!-- Create new share -->
<NcButton v-if="canReshare"
class="new-link-share"
:disabled="loading"
@click.prevent.stop="onNewLinkShare">
{{ t('files_sharing', 'Create a link share') }}
<template #icon>
<LoadingIcon v-if="loading" :size="20" />
<LinkIcon v-else :size="20" />
</template>
</NcButton>
</section>
<section v-if="sections.length > 0 && !showSharingDetailsView">
@ -188,12 +199,19 @@ import SharingDetailsTab from './SharingDetailsTab.vue'
import ShareDetails from '../mixins/ShareDetails.js'
import logger from '../services/logger.ts'
import LinkIcon from 'vue-material-design-icons/Link.vue'
import LoadingIcon from 'vue-material-design-icons/Loading.vue'
import PendingActionsHandlersMixin from '../mixins/PendingActionsHandlersMixin.ts'
import logger from '../logger.ts'
export default {
name: 'SharingTab',
components: {
CollectionList,
InfoIcon,
LinkIcon,
LoadingIcon,
NcAvatar,
NcButton,
NcPopover,
@ -205,7 +223,7 @@ export default {
SharingList,
SharingDetailsTab,
},
mixins: [ShareDetails],
mixins: [ShareDetails, PendingActionsHandlersMixin],
data() {
return {
@ -222,7 +240,7 @@ export default {
sharedWithMe: {},
shares: [],
linkShares: [],
externalShares: [],
logger,
sections: OCA.Sharing.ShareTabSections.getSections(),
projectsEnabled: loadState('core', 'projects_enabled', false),
@ -592,6 +610,11 @@ export default {
}
.new-link-share {
width: 100%;
height: 44px;
}
}
& > section:not(:last-child) {