diff options
Diffstat (limited to 'apps/files_sharing/src/views/SharingDetailsTab.vue')
-rw-r--r-- | apps/files_sharing/src/views/SharingDetailsTab.vue | 461 |
1 files changed, 302 insertions, 159 deletions
diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index 9922caf4f4e..b3a3b95d92e 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -8,7 +8,7 @@ <span> <NcAvatar v-if="isUserShare" class="sharing-entry__avatar" - :is-no-user="share.shareType !== SHARE_TYPES.SHARE_TYPE_USER" + :is-no-user="share.shareType !== ShareType.User" :user="share.shareWith" :display-name="share.shareWithDisplayName" :menu-position="'left'" @@ -38,7 +38,7 @@ <NcCheckboxRadioSwitch :button-variant="true" data-cy-files-sharing-share-permissions-bundle="upload-edit" :checked.sync="sharingPermission" - :value="bundledPermissions.ALL.toString()" + :value="allPermissions" name="sharing_permission_radio" type="radio" button-variant-grouped="vertical" @@ -62,7 +62,7 @@ type="radio" button-variant-grouped="vertical" @update:checked="toggleCustomPermissions"> - {{ t('files_sharing', 'File drop') }} + {{ t('files_sharing', 'File request') }} <small class="subline">{{ t('files_sharing', 'Upload only') }}</small> <template #icon> <UploadIcon :size="20" /> @@ -105,19 +105,33 @@ role="region"> <section> <NcInputField v-if="isPublicShare" + class="sharingTabDetailsView__label" autocomplete="off" :label="t('files_sharing', 'Share label')" :value.sync="share.label" /> + <NcInputField v-if="config.allowCustomTokens && isPublicShare && !isNewShare" + autocomplete="off" + :label="t('files_sharing', 'Share link token')" + :helper-text="t('files_sharing', 'Set the public share link token to something easy to remember or generate a new token. It is not recommended to use a guessable token for shares which contain sensitive information.')" + show-trailing-button + :trailing-button-label="loadingToken ? t('files_sharing', 'Generating…') : t('files_sharing', 'Generate new token')" + :value.sync="share.token" + @trailing-button-click="generateNewToken"> + <template #trailing-button-icon> + <NcLoadingIcon v-if="loadingToken" /> + <Refresh v-else :size="20" /> + </template> + </NcInputField> <template v-if="isPublicShare"> <NcCheckboxRadioSwitch :checked.sync="isPasswordProtected" :disabled="isPasswordEnforced"> {{ t('files_sharing', 'Set password') }} </NcCheckboxRadioSwitch> <NcPasswordField v-if="isPasswordProtected" autocomplete="new-password" - :value="hasUnsavedPassword ? share.newPassword : ''" + :value="share.newPassword ?? ''" :error="passwordError" - :helper-text="errorPasswordLabel" - :required="isPasswordEnforced" + :helper-text="errorPasswordLabel || passwordHint" + :required="isPasswordEnforced && isNewShare" :label="t('files_sharing', 'Password')" @update:value="onPasswordChange" /> @@ -144,7 +158,8 @@ :value="new Date(share.expireDate ?? dateTomorrow)" :min="dateTomorrow" :max="maxExpirationDateEnforced" - :hide-label="true" + hide-label + :label="t('files_sharing', 'Expiration date')" :placeholder="t('files_sharing', 'Expiration date')" type="date" @input="onExpirationChange" /> @@ -154,21 +169,24 @@ @update:checked="queueUpdate('hideDownload')"> {{ t('files_sharing', 'Hide download') }} </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="!isPublicShare" + <NcCheckboxRadioSwitch v-else :disabled="!canSetDownload" :checked.sync="canDownload" data-cy-files-sharing-share-permissions-checkbox="download"> - {{ t('files_sharing', 'Allow download') }} + {{ t('files_sharing', 'Allow download and sync') }} </NcCheckboxRadioSwitch> <NcCheckboxRadioSwitch :checked.sync="writeNoteToRecipientIsChecked"> {{ t('files_sharing', 'Note to recipient') }} </NcCheckboxRadioSwitch> <template v-if="writeNoteToRecipientIsChecked"> - <label for="share-note-textarea"> - {{ t('files_sharing', 'Enter a note for the share recipient') }} - </label> - <textarea id="share-note-textarea" :value="share.note" @input="share.note = $event.target.value" /> + <NcTextArea :label="t('files_sharing', 'Note to recipient')" + :placeholder="t('files_sharing', 'Enter a note for the share recipient')" + :value.sync="share.note" /> </template> + <NcCheckboxRadioSwitch v-if="isPublicShare && isFolder" + :checked.sync="showInGridView"> + {{ t('files_sharing', 'Show files in grid view') }} + </NcCheckboxRadioSwitch> <ExternalShareAction v-for="action in externalLinkActions" :id="action.id" ref="externalLinkActions" @@ -180,7 +198,7 @@ {{ t('files_sharing', 'Custom permissions') }} </NcCheckboxRadioSwitch> <section v-if="setCustomPermissions" class="custom-permissions-group"> - <NcCheckboxRadioSwitch :disabled="!allowsFileDrop && share.type === SHARE_TYPES.SHARE_TYPE_LINK" + <NcCheckboxRadioSwitch :disabled="!canRemoveReadPermission" :checked.sync="hasRead" data-cy-files-sharing-share-permissions-checkbox="read"> {{ t('files_sharing', 'Read') }} @@ -196,7 +214,7 @@ data-cy-files-sharing-share-permissions-checkbox="update"> {{ t('files_sharing', 'Edit') }} </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="config.isResharingAllowed && share.type !== SHARE_TYPES.SHARE_TYPE_LINK" + <NcCheckboxRadioSwitch v-if="resharingIsPossible" :disabled="!canSetReshare" :checked.sync="canReshare" data-cy-files-sharing-share-permissions-checkbox="share"> @@ -208,19 +226,6 @@ {{ t('files_sharing', 'Delete') }} </NcCheckboxRadioSwitch> </section> - <div class="sharingTabDetailsView__delete"> - <NcButton v-if="!isNewShare" - :aria-label="t('files_sharing', 'Delete share')" - :disabled="false" - :readonly="false" - type="tertiary" - @click.prevent="removeShare"> - <template #icon> - <CloseIcon :size="16" /> - </template> - {{ t('files_sharing', 'Delete share') }} - </NcButton> - </div> </section> </div> </div> @@ -228,11 +233,25 @@ <div class="sharingTabDetailsView__footer"> <div class="button-group"> <NcButton data-cy-files-sharing-share-editor-action="cancel" - @click="$emit('close-sharing-details')"> + @click="cancel"> {{ t('files_sharing', 'Cancel') }} </NcButton> + <div class="sharingTabDetailsView__delete"> + <NcButton v-if="!isNewShare" + :aria-label="t('files_sharing', 'Delete share')" + :disabled="false" + :readonly="false" + variant="tertiary" + @click.prevent="removeShare"> + <template #icon> + <CloseIcon :size="20" /> + </template> + {{ t('files_sharing', 'Delete share') }} + </NcButton> + </div> <NcButton type="primary" data-cy-files-sharing-share-editor-action="save" + :disabled="creating" @click="saveShare"> {{ shareButtonText }} <template v-if="creating" #icon> @@ -245,19 +264,24 @@ </template> <script> +import { emit } from '@nextcloud/event-bus' import { getLanguage } from '@nextcloud/l10n' -import { Type as ShareType } from '@nextcloud/sharing' - -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' -import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js' -import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' +import { ShareType } from '@nextcloud/sharing' +import { showError } from '@nextcloud/dialogs' +import moment from '@nextcloud/moment' + +import NcAvatar from '@nextcloud/vue/components/NcAvatar' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import NcDateTimePickerNative from '@nextcloud/vue/components/NcDateTimePickerNative' +import NcInputField from '@nextcloud/vue/components/NcInputField' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' +import NcPasswordField from '@nextcloud/vue/components/NcPasswordField' +import NcTextArea from '@nextcloud/vue/components/NcTextArea' + import CircleIcon from 'vue-material-design-icons/CircleOutline.vue' import CloseIcon from 'vue-material-design-icons/Close.vue' -import EditIcon from 'vue-material-design-icons/Pencil.vue' +import EditIcon from 'vue-material-design-icons/PencilOutline.vue' import EmailIcon from 'vue-material-design-icons/Email.vue' import LinkIcon from 'vue-material-design-icons/Link.vue' import GroupIcon from 'vue-material-design-icons/AccountGroup.vue' @@ -268,14 +292,16 @@ import UploadIcon from 'vue-material-design-icons/Upload.vue' import MenuDownIcon from 'vue-material-design-icons/MenuDown.vue' import MenuUpIcon from 'vue-material-design-icons/MenuUp.vue' import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue' +import Refresh from 'vue-material-design-icons/Refresh.vue' import ExternalShareAction from '../components/ExternalShareAction.vue' -import GeneratePassword from '../utils/GeneratePassword.js' -import Share from '../models/Share.js' +import GeneratePassword from '../utils/GeneratePassword.ts' +import Share from '../models/Share.ts' import ShareRequests from '../mixins/ShareRequests.js' -import ShareTypes from '../mixins/ShareTypes.js' import SharesMixin from '../mixins/SharesMixin.js' +import { generateToken } from '../services/TokenService.ts' +import logger from '../services/logger.ts' import { ATOMIC_PERMISSIONS, @@ -288,11 +314,12 @@ export default { components: { NcAvatar, NcButton, - NcInputField, - NcPasswordField, - NcDateTimePickerNative, NcCheckboxRadioSwitch, + NcDateTimePickerNative, + NcInputField, NcLoadingIcon, + NcPasswordField, + NcTextArea, CloseIcon, CircleIcon, EditIcon, @@ -306,8 +333,9 @@ export default { MenuDownIcon, MenuUpIcon, DotsHorizontalIcon, + Refresh, }, - mixins: [ShareTypes, ShareRequests, SharesMixin], + mixins: [ShareRequests, SharesMixin], props: { shareRequestValue: { type: Object, @@ -334,6 +362,8 @@ export default { isFirstComponentLoad: true, test: false, creating: false, + initialToken: this.share.token, + loadingToken: false, ExternalShareActions: OCA.Sharing.ExternalShareActions.state, } @@ -342,34 +372,40 @@ export default { computed: { title() { switch (this.share.type) { - case this.SHARE_TYPES.SHARE_TYPE_USER: - return t('files_sharing', 'Share with {userName}', { userName: this.share.shareWithDisplayName }) - case this.SHARE_TYPES.SHARE_TYPE_EMAIL: + case ShareType.User: + return t('files_sharing', 'Share with {user}', { user: this.share.shareWithDisplayName }) + case ShareType.Email: return t('files_sharing', 'Share with email {email}', { email: this.share.shareWith }) - case this.SHARE_TYPES.SHARE_TYPE_LINK: + case ShareType.Link: return t('files_sharing', 'Share link') - case this.SHARE_TYPES.SHARE_TYPE_GROUP: + case ShareType.Group: return t('files_sharing', 'Share with group') - case this.SHARE_TYPES.SHARE_TYPE_ROOM: + case ShareType.Room: return t('files_sharing', 'Share in conversation') - case this.SHARE_TYPES.SHARE_TYPE_REMOTE: { + case ShareType.Remote: { const [user, server] = this.share.shareWith.split('@') + if (this.config.showFederatedSharesAsInternal) { + return t('files_sharing', 'Share with {user}', { user }) + } return t('files_sharing', 'Share with {user} on remote server {server}', { user, server }) } - case this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP: + case ShareType.RemoteGroup: return t('files_sharing', 'Share with remote group') - case this.SHARE_TYPES.SHARE_TYPE_GUEST: + case ShareType.Guest: return t('files_sharing', 'Share with guest') default: { - if (this.share.id) { - // Share already exists - return t('files_sharing', 'Update share') - } else { - return t('files_sharing', 'Create share') - } + if (this.share.id) { + // Share already exists + return t('files_sharing', 'Update share') + } else { + return t('files_sharing', 'Create share') + } } } }, + allPermissions() { + return this.isFolder ? this.bundledPermissions.ALL.toString() : this.bundledPermissions.ALL_FILE.toString() + }, /** * Can the sharee edit the shared file ? */ @@ -414,24 +450,34 @@ export default { this.updateAtomicPermissions({ isReshareChecked: checked }) }, }, + + /** + * Change the default view for public shares from "list" to "grid" + */ + showInGridView: { + get() { + return this.getShareAttribute('config', 'grid_view', false) + }, + /** @param {boolean} value If the default view should be changed to "grid" */ + set(value) { + this.setShareAttribute('config', 'grid_view', value) + }, + }, + /** * Can the sharee download files or only view them ? */ canDownload: { get() { - return this.share.attributes.find(attr => attr.key === 'download')?.enabled || false + return this.getShareAttribute('permissions', 'download', true) }, set(checked) { - // Find the 'download' attribute and update its value - const downloadAttr = this.share.attributes.find(attr => attr.key === 'download') - if (downloadAttr) { - downloadAttr.enabled = checked - } + this.setShareAttribute('permissions', 'download', checked) }, }, /** * Is this share readable - * Needed for some federated shares that might have been added from file drop links + * Needed for some federated shares that might have been added from file requests links */ hasRead: { get() { @@ -457,26 +503,6 @@ export default { }, }, /** - * Is the current share password protected ? - * - * @return {boolean} - */ - isPasswordProtected: { - get() { - return this.config.enforcePasswordForPublicLink - || !!this.share.password - }, - async set(enabled) { - if (enabled) { - this.share.password = await GeneratePassword() - this.$set(this.share, 'newPassword', this.share.password) - } else { - this.share.password = '' - this.$delete(this.share, 'newPassword') - } - }, - }, - /** * Is the current share a folder ? * * @return {boolean} @@ -517,17 +543,14 @@ export default { return new Date(new Date().setDate(new Date().getDate() + 1)) }, isUserShare() { - return this.share.type === this.SHARE_TYPES.SHARE_TYPE_USER + return this.share.type === ShareType.User }, isGroupShare() { - return this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP - }, - isNewShare() { - return !this.share.id + return this.share.type === ShareType.Group }, allowsFileDrop() { if (this.isFolder && this.config.isPublicUploadEnabled) { - if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK || this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL) { + if (this.share.type === ShareType.Link || this.share.type === ShareType.Email) { return true } } @@ -543,6 +566,9 @@ export default { return t('files_sharing', 'Update share') }, + resharingIsPossible() { + return this.config.isResharingAllowed && this.share.type !== ShareType.Link && this.share.type !== ShareType.Email + }, /** * Can the sharer set whether the sharee can edit the file ? * @@ -600,6 +626,12 @@ export default { // allowed to revoke it too (but not to grant it again). return (this.fileInfo.canDownload() || this.canDownload) }, + canRemoveReadPermission() { + return this.allowsFileDrop && ( + this.share.type === ShareType.Link + || this.share.type === ShareType.Email + ) + }, // if newPassword exists, but is empty, it means // the user deleted the original password hasUnsavedPassword() { @@ -656,7 +688,7 @@ export default { */ isEmailShareType() { return this.share - ? this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL + ? this.share.type === ShareType.Email : false }, canTogglePasswordProtectedByTalkAvailable() { @@ -673,7 +705,7 @@ export default { return OC.appswebroots.spreed !== undefined }, canChangeHideDownload() { - const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.enabled === false + const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.value === false return this.fileInfo.shareAttributes.some(hasDisabledDownload) }, customPermissionsList() { @@ -686,8 +718,15 @@ export default { [ATOMIC_PERMISSIONS.DELETE]: this.t('files_sharing', 'Delete'), } - return [ATOMIC_PERMISSIONS.READ, ATOMIC_PERMISSIONS.CREATE, ATOMIC_PERMISSIONS.UPDATE, ATOMIC_PERMISSIONS.SHARE, ATOMIC_PERMISSIONS.DELETE] - .filter((permission) => hasPermissions(this.share.permissions, permission)) + const permissionsList = [ + ATOMIC_PERMISSIONS.READ, + ...(this.isFolder ? [ATOMIC_PERMISSIONS.CREATE] : []), + ATOMIC_PERMISSIONS.UPDATE, + ...(this.resharingIsPossible ? [ATOMIC_PERMISSIONS.SHARE] : []), + ...(this.isFolder ? [ATOMIC_PERMISSIONS.DELETE] : []), + ] + + return permissionsList.filter((permission) => hasPermissions(this.share.permissions, permission)) .map((permission, index) => index === 0 ? translatedPermissions[permission] : translatedPermissions[permission].toLocaleLowerCase(getLanguage())) @@ -698,18 +737,25 @@ export default { }, errorPasswordLabel() { if (this.passwordError) { - return t('files_sharing', "Password field can't be empty") + return t('files_sharing', 'Password field cannot be empty') } return undefined }, + passwordHint() { + if (this.isNewShare || this.hasUnsavedPassword) { + return undefined + } + return t('files_sharing', 'Replace current password') + }, + /** * Additional actions for the menu * * @return {Array} */ externalLinkActions() { - const filterValidAction = (action) => (action.shareType.includes(ShareType.SHARE_TYPE_LINK) || action.shareType.includes(ShareType.SHARE_TYPE_EMAIL)) && action.advanced + const filterValidAction = (action) => (action.shareType.includes(ShareType.Link) || action.shareType.includes(ShareType.Email)) && action.advanced // filter only the advanced registered actions for said link return this.ExternalShareActions.actions .filter(filterValidAction) @@ -727,8 +773,8 @@ export default { beforeMount() { this.initializePermissions() this.initializeAttributes() - console.debug('shareSentIn', this.share) - console.debug('config', this.config) + logger.debug('Share object received', { share: this.share }) + logger.debug('Configuration object received', { config: this.config }) }, mounted() { @@ -736,6 +782,60 @@ export default { }, methods: { + /** + * Set a share attribute on the current share + * @param {string} scope The attribute scope + * @param {string} key The attribute key + * @param {boolean} value The value + */ + setShareAttribute(scope, key, value) { + if (!this.share.attributes) { + this.$set(this.share, 'attributes', []) + } + + const attribute = this.share.attributes + .find((attr) => attr.scope === scope || attr.key === key) + + if (attribute) { + attribute.value = value + } else { + this.share.attributes.push({ + scope, + key, + value, + }) + } + }, + + /** + * Get the value of a share attribute + * @param {string} scope The attribute scope + * @param {string} key The attribute key + * @param {undefined|boolean} fallback The fallback to return if not found + */ + getShareAttribute(scope, key, fallback = undefined) { + const attribute = this.share.attributes?.find((attr) => attr.scope === scope && attr.key === key) + return attribute?.value ?? fallback + }, + + async generateNewToken() { + if (this.loadingToken) { + return + } + this.loadingToken = true + try { + this.share.token = await generateToken() + } catch (error) { + showError(t('files_sharing', 'Failed to generate a new token')) + } + this.loadingToken = false + }, + + cancel() { + this.share.token = this.initialToken + this.$emit('close-sharing-details') + }, + updateAtomicPermissions({ isReadChecked = this.hasRead, isEditChecked = this.canEdit, @@ -744,6 +844,13 @@ export default { isReshareChecked = this.canReshare, } = {}) { // calc permissions if checked + + if (!this.isFolder && (isCreateChecked || isDeleteChecked)) { + logger.debug('Ignoring create/delete permissions for file share — only available for folders') + isCreateChecked = false + isDeleteChecked = false + } + const permissions = 0 | (isReadChecked ? ATOMIC_PERMISSIONS.READ : 0) | (isCreateChecked ? ATOMIC_PERMISSIONS.CREATE : 0) @@ -766,8 +873,8 @@ export default { async initializeAttributes() { if (this.isNewShare) { - if (this.isPasswordEnforced && this.isPublicShare) { - this.$set(this.share, 'newPassword', await GeneratePassword()) + if ((this.config.enableLinkPasswordByDefault || this.isPasswordEnforced) && this.isPublicShare) { + this.$set(this.share, 'newPassword', await GeneratePassword(true)) this.advancedSectionAccordionExpanded = true } /* Set default expiration dates if configured */ @@ -800,6 +907,11 @@ export default { this.advancedSectionAccordionExpanded = true } + if (this.isValidShareAttribute(this.share.note)) { + this.writeNoteToRecipientIsChecked = true + this.advancedSectionAccordionExpanded = true + } + }, handleShareType() { if ('shareType' in this.share) { @@ -820,6 +932,10 @@ export default { this.setCustomPermissions = true } } + // Read permission required for share creation + if (!this.canRemoveReadPermission) { + this.hasRead = true + } }, handleCustomPermissions() { if (!this.isNewShare && (this.hasCustomPermissions || this.share.setCustomPermissions)) { @@ -838,6 +954,9 @@ export default { async saveShare() { const permissionsAndAttributes = ['permissions', 'attributes', 'note', 'expireDate'] const publicShareAttributes = ['label', 'password', 'hideDownload'] + if (this.config.allowCustomTokens) { + publicShareAttributes.push('token') + } if (this.isPublicShare) { permissionsAndAttributes.push(...publicShareAttributes) } @@ -856,10 +975,7 @@ export default { this.share.note = '' } if (this.isPasswordProtected) { - if (this.hasUnsavedPassword && this.isValidShareAttribute(this.share.newPassword)) { - this.share.password = this.share.newPassword - this.$delete(this.share, 'newPassword') - } else if (this.isPasswordEnforced && !this.isValidShareAttribute(this.share.password)) { + if (this.isPasswordEnforced && this.isNewShare && !this.isValidShareAttribute(this.share.password)) { this.passwordError = true } } else { @@ -883,19 +999,45 @@ export default { incomingShare.expireDate = this.hasExpirationDate ? this.share.expireDate : '' if (this.isPasswordProtected) { - incomingShare.password = this.share.password + incomingShare.password = this.share.newPassword + } + + let share + try { + this.creating = true + share = await this.addShare(incomingShare) + } catch (error) { + this.creating = false + // Error is already handled by ShareRequests mixin + return + } + + // ugly hack to make code work - we need the id to be set but at the same time we need to keep values we want to update + this.share._share.id = share.id + await this.queueUpdate(...permissionsAndAttributes) + // Also a ugly hack to update the updated permissions + for (const prop of permissionsAndAttributes) { + if (prop in share && prop in this.share) { + try { + share[prop] = this.share[prop] + } catch { + share._share[prop] = this.share[prop] + } + } } - this.creating = true - const share = await this.addShare(incomingShare, this.fileInfo) - this.creating = false this.share = share + this.creating = false this.$emit('add:share', this.share) } else { + // Let's update after creation as some attrs are only available after creation + await this.queueUpdate(...permissionsAndAttributes) this.$emit('update:share', this.share) - this.queueUpdate(...permissionsAndAttributes) } + await this.getNode() + emit('files:node:updated', this.node) + if (this.$refs.externalLinkActions?.length > 0) { await Promise.allSettled(this.$refs.externalLinkActions.map((action) => { if (typeof action.$children.at(0)?.onSave !== 'function') { @@ -911,12 +1053,11 @@ export default { * Process the new share request * * @param {Share} share incoming share object - * @param {object} fileInfo file data */ - async addShare(share, fileInfo) { - console.debug('Adding a new share from the input for', share) + async addShare(share) { + logger.debug('Adding a new share from the input for', { share }) + const path = this.path try { - const path = (fileInfo.path + '/' + fileInfo.name).replace('//', '/') const resultingShare = await this.createShare({ path, shareType: share.shareType, @@ -929,13 +1070,15 @@ export default { }) return resultingShare } catch (error) { - console.error('Error while adding new share', error) + logger.error('Error while adding new share', { error }) } finally { // this.loading = false // No loader here yet } }, async removeShare() { await this.onDelete() + await this.getNode() + emit('files:node:updated', this.node) this.$emit('close-sharing-details') }, /** @@ -949,6 +1092,11 @@ export default { * @param {string} password the changed password */ onPasswordChange(password) { + if (password === '') { + this.$delete(this.share, 'newPassword') + this.passwordError = this.isNewShare && this.isPasswordEnforced + return + } this.passwordError = !this.isValidShareAttribute(password) this.$set(this.share, 'newPassword', password) }, @@ -961,10 +1109,6 @@ export default { * "sendPasswordByTalk". */ onPasswordProtectedByTalkChange() { - if (this.hasUnsavedPassword) { - this.share.password = this.share.newPassword.trim() - } - this.queueUpdate('sendPasswordByTalk', 'password') }, isValidShareAttribute(value) { @@ -980,22 +1124,22 @@ export default { }, getShareTypeIcon(type) { switch (type) { - case this.SHARE_TYPES.SHARE_TYPE_LINK: + case ShareType.Link: return LinkIcon - case this.SHARE_TYPES.SHARE_TYPE_GUEST: + case ShareType.Guest: return UserIcon - case this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP: - case this.SHARE_TYPES.SHARE_TYPE_GROUP: + case ShareType.RemoteGroup: + case ShareType.Group: return GroupIcon - case this.SHARE_TYPES.SHARE_TYPE_EMAIL: + case ShareType.Email: return EmailIcon - case this.SHARE_TYPES.SHARE_TYPE_CIRCLE: + case ShareType.Team: return CircleIcon - case this.SHARE_TYPES.SHARE_TYPE_ROOM: + case ShareType.Room: return ShareIcon - case this.SHARE_TYPES.SHARE_TYPE_DECK: + case ShareType.Deck: return ShareIcon - case this.SHARE_TYPES.SHARE_TYPE_SCIENCEMESH: + case ShareType.ScienceMesh: return ShareIcon default: return null // Or a default icon component if needed @@ -1027,7 +1171,7 @@ export default { h1 { font-size: 15px; - padding-left: 0.3em; + padding-inline-start: 0.3em; } } @@ -1038,7 +1182,7 @@ export default { overflow: scroll; flex-shrink: 1; padding: 4px; - padding-right: 12px; + padding-inline-end: 12px; } &__quick-permissions { @@ -1060,12 +1204,9 @@ export default { padding: 0.1em; } - ::v-deep label { - - span { - display: flex; - flex-direction: column; - } + :deep(label span) { + display: flex; + flex-direction: column; } /* Target component based style in NcCheckboxRadioSwitch slot content*/ @@ -1094,8 +1235,8 @@ export default { &__advanced { width: 100%; margin-bottom: 0.5em; - text-align: left; - padding-left: 0; + text-align: start; + padding-inline-start: 0; section { @@ -1110,30 +1251,32 @@ export default { } /* - The following style is applied out of the component's scope - to remove padding from the label.checkbox-radio-switch__label, - which is used to group radio checkbox items. The use of ::v-deep - ensures that the padding is modified without being affected by - the component's scoping. - Without this achieving left alignment for the checkboxes would not - be possible. - */ - span { - ::v-deep label { - padding-left: 0 !important; - background-color: initial !important; - border: none !important; - } + The following style is applied out of the component's scope + to remove padding from the label.checkbox-radio-switch__label, + which is used to group radio checkbox items. The use of ::v-deep + ensures that the padding is modified without being affected by + the component's scoping. + Without this achieving left alignment for the checkboxes would not + be possible. + */ + span :deep(label) { + padding-inline-start: 0 !important; + background-color: initial !important; + border: none !important; } section.custom-permissions-group { - padding-left: 1.5em; + padding-inline-start: 1.5em; } } } + &__label { + padding-block-end: 6px; + } + &__delete { - >button:first-child { + > button:first-child { color: rgb(223, 7, 7); } } @@ -1155,10 +1298,10 @@ export default { margin-top: 16px; button { - margin-left: 16px; + margin-inline-start: 16px; &:first-child { - margin-left: 0; + margin-inline-start: 0; } } } |