mirror of
https://github.com/nextcloud/server.git
synced 2026-06-12 18:21:40 -04:00
feat: New create link share button
Signed-off-by: nfebe <fenn25.fn@gmail.com>
This commit is contained in:
parent
d8dc0611aa
commit
16b98e7493
5 changed files with 549 additions and 209 deletions
235
apps/files_sharing/src/components/PendingActions.vue
Normal file
235
apps/files_sharing/src/components/PendingActions.vue
Normal 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>
|
||||
|
|
@ -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) : ''
|
||||
},
|
||||
|
||||
|
|
|
|||
11
apps/files_sharing/src/logger.ts
Normal file
11
apps/files_sharing/src/logger.ts
Normal 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()
|
||||
239
apps/files_sharing/src/mixins/PendingActionsHandlersMixin.ts
Normal file
239
apps/files_sharing/src/mixins/PendingActionsHandlersMixin.ts
Normal 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)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue