aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/src/components/SharingEntryLink.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_sharing/src/components/SharingEntryLink.vue')
-rw-r--r--apps/files_sharing/src/components/SharingEntryLink.vue155
1 files changed, 84 insertions, 71 deletions
diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue
index 747fa31aac9..6a456fa0a15 100644
--- a/apps/files_sharing/src/components/SharingEntryLink.vue
+++ b/apps/files_sharing/src/components/SharingEntryLink.vue
@@ -24,20 +24,26 @@
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
</div>
- <!-- clipboard -->
- <NcActions v-if="share && (!isEmailShareType || isFileRequest) && share.token" ref="copyButton" class="sharing-entry__copy">
- <NcActionButton :aria-label="copyLinkTooltip"
- :title="copyLinkTooltip"
- :href="shareLink"
- @click.prevent="copyLink">
- <template #icon>
- <CheckIcon v-if="copied && copySuccess"
- :size="20"
- class="icon-checkmark-color" />
- <ClipboardIcon v-else :size="20" />
- </template>
- </NcActionButton>
- </NcActions>
+ <div class="sharing-entry__actions">
+ <ShareExpiryTime v-if="share && share.expireDate" :share="share" />
+
+ <!-- clipboard -->
+ <div>
+ <NcActions v-if="share && (!isEmailShareType || isFileRequest) && share.token" ref="copyButton" class="sharing-entry__copy">
+ <NcActionButton :aria-label="copyLinkTooltip"
+ :title="copyLinkTooltip"
+ :href="shareLink"
+ @click.prevent="copyLink">
+ <template #icon>
+ <CheckIcon v-if="copied && copySuccess"
+ :size="20"
+ class="icon-checkmark-color" />
+ <ClipboardIcon v-else :size="20" />
+ </template>
+ </NcActionButton>
+ </NcActions>
+ </div>
+ </div>
</div>
<!-- pending actions -->
@@ -68,10 +74,10 @@
{{ config.enforcePasswordForPublicLink ? t('files_sharing', 'Password protection (enforced)') : t('files_sharing', 'Password protection') }}
</NcActionCheckbox>
- <NcActionInput v-if="pendingEnforcedPassword || share.password"
+ <NcActionInput v-if="pendingEnforcedPassword || isPasswordProtected"
class="share-link-password"
:label="t('files_sharing', 'Enter a password')"
- :value.sync="share.password"
+ :value.sync="share.newPassword"
:disabled="saving"
:required="config.enableLinkPasswordByDefault || config.enforcePasswordForPublicLink"
:minlength="isPasswordPolicyEnabled && config.passwordPolicy.minLength"
@@ -86,12 +92,13 @@
:checked.sync="defaultExpirationDateEnabled"
:disabled="pendingEnforcedExpirationDate || saving"
class="share-link-expiration-date-checkbox"
- @change="onDefaultExpirationDateEnabledChange">
- {{ config.enforcePasswordForPublicLink ? t('files_sharing', 'Enable link expiration (enforced)') : t('files_sharing', 'Enable link expiration') }}
+ @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"
@@ -101,13 +108,15 @@
type="date"
:min="dateTomorrow"
:max="maxExpirationDateEnforced"
- @input="onExpirationChange /* let's not submit when picked, the user might want to still edit or copy the password */">
+ @update:model-value="onExpirationChange"
+ @change="expirationDateChanged">
<template #icon>
<IconCalendarBlank :size="20" />
</template>
</NcActionInput>
- <NcActionButton @click.prevent.stop="onNewLinkShare(true)">
+ <NcActionButton :disabled="pendingEnforcedPassword && !share.newPassword"
+ @click.prevent.stop="onNewLinkShare(true)">
<template #icon>
<CheckIcon :size="20" />
</template>
@@ -215,42 +224,43 @@
</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 moment from '@nextcloud/moment'
-import Vue from 'vue'
-
-import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
-import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
-import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
-import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
-import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.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 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 Tune from 'vue-material-design-icons/Tune.vue'
-import IconCalendarBlank from 'vue-material-design-icons/CalendarBlank.vue'
+import IconCalendarBlank from 'vue-material-design-icons/CalendarBlankOutline.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 LockIcon from 'vue-material-design-icons/LockOutline.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'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import SharingEntryQuickShareSelect from './SharingEntryQuickShareSelect.vue'
+import ShareExpiryTime from './ShareExpiryTime.vue'
import ExternalShareAction from './ExternalShareAction.vue'
import GeneratePassword from '../utils/GeneratePassword.ts'
import Share from '../models/Share.ts'
import SharesMixin from '../mixins/SharesMixin.js'
import ShareDetails from '../mixins/ShareDetails.js'
-import { getLoggerBuilder } from '@nextcloud/logger'
+import logger from '../services/logger.ts'
export default {
name: 'SharingEntryLink',
@@ -277,6 +287,7 @@ export default {
CloseIcon,
PlusIcon,
SharingEntryQuickShareSelect,
+ ShareExpiryTime,
},
mixins: [SharesMixin, ShareDetails],
@@ -304,10 +315,6 @@ export default {
ExternalLegacyLinkActions: OCA.Sharing.ExternalLinkActions.state,
ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
- logger: getLoggerBuilder()
- .setApp('files_sharing')
- .detectUser()
- .build(),
// tracks whether modal should be opened or not
showQRCode: false,
@@ -321,6 +328,8 @@ export default {
* @return {string}
*/
title() {
+ const l10nOptions = { escape: false /* no escape as this string is already escaped by Vue */ }
+
// if we have a valid existing share (not pending)
if (this.share && this.share.id) {
if (!this.isShareOwner && this.share.ownerDisplayName) {
@@ -328,26 +337,26 @@ export default {
return t('files_sharing', '{shareWith} by {initiator}', {
shareWith: this.share.shareWith,
initiator: this.share.ownerDisplayName,
- })
+ }, l10nOptions)
}
return t('files_sharing', 'Shared via link by {initiator}', {
initiator: this.share.ownerDisplayName,
- })
+ }, l10nOptions)
}
if (this.share.label && this.share.label.trim() !== '') {
if (this.isEmailShareType) {
if (this.isFileRequest) {
return t('files_sharing', 'File request ({label})', {
label: this.share.label.trim(),
- })
+ }, l10nOptions)
}
return t('files_sharing', 'Mail share ({label})', {
label: this.share.label.trim(),
- })
+ }, l10nOptions)
}
return t('files_sharing', 'Share link ({label})', {
label: this.share.label.trim(),
- })
+ }, l10nOptions)
}
if (this.isEmailShareType) {
if (!this.share.shareWith || this.share.shareWith.trim() === '') {
@@ -382,22 +391,6 @@ export default {
}
return null
},
- /**
- * Is the current share password protected ?
- *
- * @return {boolean}
- */
- isPasswordProtected: {
- get() {
- return this.config.enforcePasswordForPublicLink
- || !!this.share.password
- },
- async set(enabled) {
- // TODO: directly save after generation to make sure the share is always protected
- Vue.set(this.share, 'password', enabled ? await GeneratePassword(true) : '')
- Vue.set(this.share, 'newPassword', this.share.password)
- },
- },
passwordExpirationTime() {
if (this.share.passwordExpirationTime === null) {
@@ -597,6 +590,9 @@ export default {
},
mounted() {
this.defaultExpirationDateEnabled = this.config.defaultExpirationDate instanceof Date
+ if (this.share && this.isNewShare) {
+ this.share.expireDate = this.defaultExpirationDateEnabled ? this.formatDateToString(this.config.defaultExpirationDate) : ''
+ }
},
methods: {
@@ -618,7 +614,7 @@ export default {
* @param {boolean} shareReviewComplete if the share was reviewed
*/
async onNewLinkShare(shareReviewComplete = false) {
- this.logger.debug('onNewLinkShare called (with this.share)', this.share)
+ logger.debug('onNewLinkShare called (with this.share)', this.share)
// do not run again if already loading
if (this.loading) {
return
@@ -633,7 +629,7 @@ export default {
shareDefaults.expiration = this.formatDateToString(this.config.defaultExpirationDate)
}
- this.logger.debug('Missing required properties?', this.enforcedPropertiesMissing)
+ logger.debug('Missing required properties?', this.enforcedPropertiesMissing)
// Do not push yet if we need a password or an expiration date: show pending menu
// A share would require a review for example is default expiration date is set but not enforced, this allows
// the user to review the share and remove the expiration date if they don't want it
@@ -641,7 +637,7 @@ export default {
this.pending = true
this.shareCreationComplete = false
- this.logger.info('Share policy requires a review or has mandated properties (password, expirationDate)...')
+ logger.info('Share policy requires a review or has mandated properties (password, expirationDate)...')
// ELSE, show the pending popovermenu
// if password default or enforced, pre-fill with random one
@@ -651,6 +647,7 @@ export default {
// create share & close menu
const share = new Share(shareDefaults)
+ share.newPassword = share.password
const component = await new Promise(resolve => {
this.$emit('add:share', share, resolve)
})
@@ -669,13 +666,13 @@ export default {
// if the share is valid, create it on the server
if (this.checkShare(this.share)) {
try {
- this.logger.info('Sending existing share to server', this.share)
+ logger.info('Sending existing share to server', this.share)
await this.pushNewLinkShare(this.share, true)
this.shareCreationComplete = true
- this.logger.info('Share created on server', this.share)
+ logger.info('Share created on server', this.share)
} catch (e) {
this.pending = false
- this.logger.error('Error creating share', e)
+ logger.error('Error creating share', e)
return false
}
return true
@@ -715,7 +712,7 @@ export default {
path,
shareType: ShareType.Link,
password: share.password,
- expireDate: share.expireDate,
+ expireDate: share.expireDate ?? '',
attributes: JSON.stringify(this.fileInfo.shareAttributes),
// we do not allow setting the publicUpload
// before the share creation.
@@ -843,7 +840,7 @@ export default {
*/
onPasswordSubmit() {
if (this.hasUnsavedPassword) {
- this.share.password = this.share.newPassword.trim()
+ this.share.newPassword = this.share.newPassword.trim()
this.queueUpdate('password')
}
},
@@ -858,7 +855,7 @@ export default {
*/
onPasswordProtectedByTalkChange() {
if (this.hasUnsavedPassword) {
- this.share.password = this.share.newPassword.trim()
+ this.share.newPassword = this.share.newPassword.trim()
}
this.queueUpdate('sendPasswordByTalk', 'password')
@@ -871,10 +868,20 @@ export default {
this.onPasswordSubmit()
this.onNoteSubmit()
},
- onDefaultExpirationDateEnabledChange(enabled) {
+
+ /**
+ * @param enabled True if expiration is enabled
+ */
+ onExpirationDateToggleUpdate(enabled) {
this.share.expireDate = enabled ? this.formatDateToString(this.config.defaultExpirationDate) : ''
},
+ expirationDateChanged(event) {
+ const value = event?.target?.value
+ const isValid = !!value && !isNaN(new Date(value).getTime())
+ this.defaultExpirationDateEnabled = isValid
+ },
+
/**
* Cancel the share creation
* Used in the pending popover
@@ -922,6 +929,12 @@ export default {
}
}
+ &__actions {
+ display: flex;
+ align-items: center;
+ margin-inline-start: auto;
+ }
+
&:not(.sharing-entry--share) &__actions {
.new-share-link {
border-top: 1px solid var(--color-border);