diff options
Diffstat (limited to 'apps/files_sharing/src/components/SharingEntryLink.vue')
-rw-r--r-- | apps/files_sharing/src/components/SharingEntryLink.vue | 155 |
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); |