diff options
author | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2023-08-31 12:22:10 +0200 |
---|---|---|
committer | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2023-08-31 12:22:10 +0200 |
commit | 7cdafeac363329cea0baa0f440923316cf2770f3 (patch) | |
tree | 62d28ce147efd55846472694adfb6870ed6b8403 /apps | |
parent | bfd7b49be7bdfbe79c3b093c6bc3cdef476c669c (diff) | |
download | nextcloud-server-7cdafeac363329cea0baa0f440923316cf2770f3.tar.gz nextcloud-server-7cdafeac363329cea0baa0f440923316cf2770f3.zip |
Revert "Improve sharing flow"
This reverts commit 8b42fb033fdcd3775b4850de6faf6091c8dcc716.
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files_sharing/src/components/SharingEntry.vue | 407 | ||||
-rw-r--r-- | apps/files_sharing/src/components/SharingEntryLink.vue | 178 | ||||
-rw-r--r-- | apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue | 186 | ||||
-rw-r--r-- | apps/files_sharing/src/components/SharingEntrySimple.vue | 4 | ||||
-rw-r--r-- | apps/files_sharing/src/components/SharingInput.vue | 10 | ||||
-rw-r--r-- | apps/files_sharing/src/lib/SharePermissionsToolBox.js | 1 | ||||
-rw-r--r-- | apps/files_sharing/src/mixins/ShareDetails.js | 43 | ||||
-rw-r--r-- | apps/files_sharing/src/mixins/ShareRequests.js | 23 | ||||
-rw-r--r-- | apps/files_sharing/src/mixins/SharesMixin.js | 28 | ||||
-rw-r--r-- | apps/files_sharing/src/models/Share.js | 2 | ||||
-rw-r--r-- | apps/files_sharing/src/utils/SharedWithMe.js | 10 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingDetailsTab.vue | 1053 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingLinkList.vue | 6 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingList.vue | 22 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingTab.vue | 49 |
15 files changed, 554 insertions, 1468 deletions
diff --git a/apps/files_sharing/src/components/SharingEntry.vue b/apps/files_sharing/src/components/SharingEntry.vue index 7399617a79c..46b65c695ee 100644 --- a/apps/files_sharing/src/components/SharingEntry.vue +++ b/apps/files_sharing/src/components/SharingEntry.vue @@ -29,64 +29,147 @@ :menu-position="'left'" :url="share.shareWithAvatar" /> - <div class="sharing-entry__summary" @click.prevent="toggleQuickShareSelect"> - <component :is="share.shareWithLink ? 'a' : 'div'" - :title="tooltip" - :aria-label="tooltip" - :href="share.shareWithLink" - class="sharing-entry__desc"> - <span>{{ title }}<span v-if="!isUnique" class="sharing-entry__desc-unique"> ({{ - share.shareWithDisplayNameUnique }})</span></span> - <p v-if="hasStatus"> - <span>{{ share.status.icon || '' }}</span> - <span>{{ share.status.message || '' }}</span> - </p> - </component> - <QuickShareSelect :share="share" - :file-info="fileInfo" - :toggle="showDropdown" - @open-sharing-details="openShareDetailsForCustomSettings(share)" /> - </div> - <NcButton class="sharing-entry__action" - :aria-label="t('files_sharing', 'Open Sharing Details')" - type="tertiary-no-background" - @click="openSharingDetails(share)"> - <template #icon> - <DotsHorizontalIcon :size="20" /> + <component :is="share.shareWithLink ? 'a' : 'div'" + :title="tooltip" + :aria-label="tooltip" + :href="share.shareWithLink" + class="sharing-entry__desc"> + <span>{{ title }}<span v-if="!isUnique" class="sharing-entry__desc-unique"> ({{ share.shareWithDisplayNameUnique }})</span></span> + <p v-if="hasStatus"> + <span>{{ share.status.icon || '' }}</span> + <span>{{ share.status.message || '' }}</span> + </p> + </component> + <NcActions menu-align="right" + class="sharing-entry__actions" + @close="onMenuClose"> + <template v-if="share.canEdit"> + <!-- edit permission --> + <NcActionCheckbox ref="canEdit" + :checked.sync="canEdit" + :value="permissionsEdit" + :disabled="saving || !canSetEdit"> + {{ t('files_sharing', 'Allow editing') }} + </NcActionCheckbox> + + <!-- create permission --> + <NcActionCheckbox v-if="isFolder" + ref="canCreate" + :checked.sync="canCreate" + :value="permissionsCreate" + :disabled="saving || !canSetCreate"> + {{ t('files_sharing', 'Allow creating') }} + </NcActionCheckbox> + + <!-- delete permission --> + <NcActionCheckbox v-if="isFolder" + ref="canDelete" + :checked.sync="canDelete" + :value="permissionsDelete" + :disabled="saving || !canSetDelete"> + {{ t('files_sharing', 'Allow deleting') }} + </NcActionCheckbox> + + <!-- reshare permission --> + <NcActionCheckbox v-if="config.isResharingAllowed" + ref="canReshare" + :checked.sync="canReshare" + :value="permissionsShare" + :disabled="saving || !canSetReshare"> + {{ t('files_sharing', 'Allow resharing') }} + </NcActionCheckbox> + + <NcActionCheckbox v-if="isSetDownloadButtonVisible" + ref="canDownload" + :checked.sync="canDownload" + :disabled="saving || !canSetDownload"> + {{ allowDownloadText }} + </NcActionCheckbox> + + <!-- expiration date --> + <NcActionCheckbox :checked.sync="hasExpirationDate" + :disabled="config.isDefaultInternalExpireDateEnforced || saving" + @uncheck="onExpirationDisable"> + {{ config.isDefaultInternalExpireDateEnforced + ? t('files_sharing', 'Expiration date enforced') + : t('files_sharing', 'Set expiration date') }} + </NcActionCheckbox> + <NcActionInput v-if="hasExpirationDate" + ref="expireDate" + :is-native-picker="true" + :hide-label="true" + :class="{ error: errors.expireDate}" + :disabled="saving" + :value="new Date(share.expireDate)" + type="date" + :min="dateTomorrow" + :max="dateMaxEnforced" + @input="onExpirationChange"> + {{ t('files_sharing', 'Enter a date') }} + </NcActionInput> + + <!-- note --> + <template v-if="canHaveNote"> + <NcActionCheckbox :checked.sync="hasNote" + :disabled="saving" + @uncheck="queueUpdate('note')"> + {{ t('files_sharing', 'Note to recipient') }} + </NcActionCheckbox> + <NcActionTextEditable v-if="hasNote" + ref="note" + :class="{ error: errors.note}" + :disabled="saving" + :value="share.newNote || share.note" + icon="icon-edit" + @update:value="onNoteChange" + @submit="onNoteSubmit" /> + </template> </template> - </NcButton> + + <NcActionButton v-if="share.canDelete" + icon="icon-close" + :disabled="saving" + @click.prevent="onDelete"> + {{ t('files_sharing', 'Unshare') }} + </NcActionButton> + </NcActions> </li> </template> <script> -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue' - -import QuickShareSelect from './SharingEntryQuickShareSelect.vue' +import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' +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 NcActionTextEditable from '@nextcloud/vue/dist/Components/NcActionTextEditable.js' import SharesMixin from '../mixins/SharesMixin.js' -import ShareDetails from '../mixins/ShareDetails.js' export default { name: 'SharingEntry', components: { - NcButton, + NcActions, + NcActionButton, + NcActionCheckbox, + NcActionInput, + NcActionTextEditable, NcAvatar, - DotsHorizontalIcon, - NcSelect, - QuickShareSelect, }, - mixins: [SharesMixin, ShareDetails], + mixins: [SharesMixin], data() { return { - showDropdown: false, + permissionsEdit: OC.PERMISSION_UPDATE, + permissionsCreate: OC.PERMISSION_CREATE, + permissionsDelete: OC.PERMISSION_DELETE, + permissionsRead: OC.PERMISSION_READ, + permissionsShare: OC.PERMISSION_SHARE, } }, + computed: { title() { let title = this.share.shareWithDisplayName @@ -103,6 +186,7 @@ export default { } return title }, + tooltip() { if (this.share.owner !== this.share.uidFileOwner) { const data = { @@ -122,6 +206,182 @@ export default { return null }, + canHaveNote() { + return !this.isRemote + }, + + isRemote() { + return this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE + || this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP + }, + + /** + * Can the sharer set whether the sharee can edit the file ? + * + * @return {boolean} + */ + canSetEdit() { + // If the owner revoked the permission after the resharer granted it + // the share still has the permission, and the resharer is still + // allowed to revoke it too (but not to grant it again). + return (this.fileInfo.sharePermissions & OC.PERMISSION_UPDATE) || this.canEdit + }, + + /** + * Can the sharer set whether the sharee can create the file ? + * + * @return {boolean} + */ + canSetCreate() { + // If the owner revoked the permission after the resharer granted it + // the share still has the permission, and the resharer is still + // allowed to revoke it too (but not to grant it again). + return (this.fileInfo.sharePermissions & OC.PERMISSION_CREATE) || this.canCreate + }, + + /** + * Can the sharer set whether the sharee can delete the file ? + * + * @return {boolean} + */ + canSetDelete() { + // If the owner revoked the permission after the resharer granted it + // the share still has the permission, and the resharer is still + // allowed to revoke it too (but not to grant it again). + return (this.fileInfo.sharePermissions & OC.PERMISSION_DELETE) || this.canDelete + }, + + /** + * Can the sharer set whether the sharee can reshare the file ? + * + * @return {boolean} + */ + canSetReshare() { + // If the owner revoked the permission after the resharer granted it + // the share still has the permission, and the resharer is still + // allowed to revoke it too (but not to grant it again). + return (this.fileInfo.sharePermissions & OC.PERMISSION_SHARE) || this.canReshare + }, + + /** + * Can the sharer set whether the sharee can download the file ? + * + * @return {boolean} + */ + canSetDownload() { + // If the owner revoked the permission after the resharer granted it + // the share still has the permission, and the resharer is still + // allowed to revoke it too (but not to grant it again). + return (this.fileInfo.canDownload() || this.canDownload) + }, + + /** + * Can the sharee edit the shared file ? + */ + canEdit: { + get() { + return this.share.hasUpdatePermission + }, + set(checked) { + this.updatePermissions({ isEditChecked: checked }) + }, + }, + + /** + * Can the sharee create the shared file ? + */ + canCreate: { + get() { + return this.share.hasCreatePermission + }, + set(checked) { + this.updatePermissions({ isCreateChecked: checked }) + }, + }, + + /** + * Can the sharee delete the shared file ? + */ + canDelete: { + get() { + return this.share.hasDeletePermission + }, + set(checked) { + this.updatePermissions({ isDeleteChecked: checked }) + }, + }, + + /** + * Can the sharee reshare the file ? + */ + canReshare: { + get() { + return this.share.hasSharePermission + }, + set(checked) { + this.updatePermissions({ isReshareChecked: checked }) + }, + }, + + /** + * Can the sharee download files or only view them ? + */ + canDownload: { + get() { + return this.share.hasDownloadPermission + }, + set(checked) { + this.updatePermissions({ isDownloadChecked: checked }) + }, + }, + + /** + * Is this share readable + * Needed for some federated shares that might have been added from file drop links + */ + hasRead: { + get() { + return this.share.hasReadPermission + }, + }, + + /** + * Is the current share a folder ? + * + * @return {boolean} + */ + isFolder() { + return this.fileInfo.type === 'dir' + }, + + /** + * Does the current share have an expiration date + * + * @return {boolean} + */ + hasExpirationDate: { + get() { + return this.config.isDefaultInternalExpireDateEnforced || !!this.share.expireDate + }, + set(enabled) { + const defaultExpirationDate = this.config.defaultInternalExpirationDate + || new Date(new Date().setDate(new Date().getDate() + 1)) + this.share.expireDate = enabled + ? this.formatDateToString(defaultExpirationDate) + : '' + console.debug('Expiration date status', enabled, this.share.expireDate) + }, + }, + + dateMaxEnforced() { + if (!this.isRemote && this.config.isDefaultInternalExpireDateEnforced) { + return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultInternalExpireDate)) + } else if (this.config.isDefaultRemoteExpireDateEnforced) { + return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultRemoteExpireDate)) + } + return null + }, + /** * @return {boolean} */ @@ -132,18 +392,70 @@ export default { return (typeof this.share.status === 'object' && !Array.isArray(this.share.status)) }, + + /** + * @return {string} + */ + allowDownloadText() { + return t('files_sharing', 'Allow download') + }, + + /** + * @return {boolean} + */ + isSetDownloadButtonVisible() { + // TODO: Implement download permission for circle shares instead of hiding the option. + // https://github.com/nextcloud/server/issues/39161 + if (this.share && this.share.type === this.SHARE_TYPES.SHARE_TYPE_CIRCLE) { + return false + } + + const allowedMimetypes = [ + // Office documents + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.presentation', + ] + + return this.isFolder || allowedMimetypes.includes(this.fileInfo.mimetype) + }, }, methods: { + updatePermissions({ + isEditChecked = this.canEdit, + isCreateChecked = this.canCreate, + isDeleteChecked = this.canDelete, + isReshareChecked = this.canReshare, + isDownloadChecked = this.canDownload, + } = {}) { + // calc permissions if checked + const permissions = 0 + | (this.hasRead ? this.permissionsRead : 0) + | (isCreateChecked ? this.permissionsCreate : 0) + | (isDeleteChecked ? this.permissionsDelete : 0) + | (isEditChecked ? this.permissionsEdit : 0) + | (isReshareChecked ? this.permissionsShare : 0) + + this.share.permissions = permissions + if (this.share.hasDownloadPermission !== isDownloadChecked) { + this.share.hasDownloadPermission = isDownloadChecked + } + this.queueUpdate('permissions', 'attributes') + }, + /** * Save potential changed data on menu close */ onMenuClose() { this.onNoteSubmit() }, - toggleQuickShareSelect() { - this.showDropdown = !this.showDropdown - }, }, } </script> @@ -153,34 +465,21 @@ export default { display: flex; align-items: center; height: 44px; - &__desc { display: flex; flex-direction: column; justify-content: space-between; - padding-bottom: 0; + padding: 8px; line-height: 1.2em; - p { color: var(--color-text-maxcontrast); } - &-unique { color: var(--color-text-maxcontrast); } } - &__actions { margin-left: auto; } - - &__summary { - padding: 8px; - display: flex; - flex-direction: column; - justify-content: center; - width: 100%; - } - } </style> diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue index 7cf0804b841..06c9cb70851 100644 --- a/apps/files_sharing/src/components/SharingEntryLink.vue +++ b/apps/files_sharing/src/components/SharingEntryLink.vue @@ -25,18 +25,13 @@ <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__desc" @click.prevent="toggleQuickShareSelect"> + <div class="sharing-entry__desc"> <span class="sharing-entry__title" :title="title"> {{ title }} </span> <p v-if="subtitle"> {{ subtitle }} </p> - <QuickShareSelect v-if="share && share.permissions !== undefined" - :share="share" - :file-info="fileInfo" - :toggle="showDropdown" - @open-sharing-details="openShareDetailsForCustomSettings(share)" /> </div> <!-- clipboard --> @@ -128,13 +123,110 @@ @close="onMenuClose"> <template v-if="share"> <template v-if="share.canEdit && canReshare"> - <NcActionButton :disabled="saving" - @click.prevent="openSharingDetails"> - <template #icon> - <Tune /> - </template> - {{ t('files_sharing', 'Customize link') }} - </NcActionButton> + <!-- Custom Label --> + <NcActionInput ref="label" + :class="{ error: errors.label }" + :disabled="saving" + :label="t('files_sharing', 'Share label')" + :value="share.newLabel !== undefined ? share.newLabel : share.label" + icon="icon-edit" + maxlength="255" + @update:value="onLabelChange" + @submit="onLabelSubmit" /> + + <SharePermissionsEditor :can-reshare="canReshare" + :share.sync="share" + :file-info="fileInfo" /> + + <NcActionSeparator /> + + <NcActionCheckbox :checked.sync="share.hideDownload" + :disabled="saving || canChangeHideDownload" + @change="queueUpdate('hideDownload')"> + {{ t('files_sharing', 'Hide download') }} + </NcActionCheckbox> + + <!-- password --> + <NcActionCheckbox :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 protect') }} + </NcActionCheckbox> + + <NcActionInput v-if="isPasswordProtected" + ref="password" + class="share-link-password" + :class="{ error: errors.password}" + :disabled="saving" + :show-trailing-button="hasUnsavedPassword" + :required="config.enforcePasswordForPublicLink" + :value="hasUnsavedPassword ? share.newPassword : '***************'" + icon="icon-password" + autocomplete="new-password" + :type="hasUnsavedPassword ? 'text': 'password'" + @update:value="onPasswordChange" + @submit="onPasswordSubmit"> + {{ t('files_sharing', 'Enter a password') }} + </NcActionInput> + <NcActionText v-if="isEmailShareType && passwordExpirationTime" icon="icon-info"> + {{ t('files_sharing', 'Password expires {passwordExpirationTime}', {passwordExpirationTime}) }} + </NcActionText> + <NcActionText v-else-if="isEmailShareType && passwordExpirationTime !== null" icon="icon-error"> + {{ t('files_sharing', 'Password expired') }} + </NcActionText> + + <!-- password protected by Talk --> + <NcActionCheckbox v-if="isPasswordProtectedByTalkAvailable" + :checked.sync="isPasswordProtectedByTalk" + :disabled="!canTogglePasswordProtectedByTalkAvailable || saving" + class="share-link-password-talk-checkbox" + @change="onPasswordProtectedByTalkChange"> + {{ t('files_sharing', 'Video verification') }} + </NcActionCheckbox> + + <!-- expiration date --> + <NcActionCheckbox :checked.sync="hasExpirationDate" + :disabled="config.isDefaultExpireDateEnforced || saving" + class="share-link-expire-date-checkbox" + @uncheck="onExpirationDisable"> + {{ config.isDefaultExpireDateEnforced + ? t('files_sharing', 'Expiration date (enforced)') + : t('files_sharing', 'Set expiration date') }} + </NcActionCheckbox> + <NcActionInput v-if="hasExpirationDate" + ref="expireDate" + :is-native-picker="true" + :hide-label="true" + class="share-link-expire-date" + :class="{ error: errors.expireDate}" + :disabled="saving" + :value="new Date(share.expireDate)" + type="date" + :min="dateTomorrow" + :max="dateMaxEnforced" + @input="onExpirationChange"> + {{ t('files_sharing', 'Enter a date') }} + </NcActionInput> + + <!-- note --> + <NcActionCheckbox :checked.sync="hasNote" + :disabled="saving" + @uncheck="queueUpdate('note')"> + {{ t('files_sharing', 'Note to recipient') }} + </NcActionCheckbox> + + <NcActionTextEditable v-if="hasNote" + ref="note" + :class="{ error: errors.note}" + :disabled="saving" + :placeholder="t('files_sharing', 'Enter a note for the share recipient')" + :value="share.newNote || share.note" + icon="icon-edit" + @update:value="onNoteChange" + @submit="onNoteSubmit" /> </template> <NcActionSeparator /> @@ -156,19 +248,18 @@ {{ name }} </NcActionLink> - <NcActionButton v-if="!isEmailShareType && canReshare" - class="new-share-link" - icon="icon-add" - @click.prevent.stop="onNewLinkShare"> - {{ t('files_sharing', 'Add another link') }} - </NcActionButton> - <NcActionButton v-if="share.canDelete" icon="icon-close" :disabled="saving" @click.prevent="onDelete"> {{ t('files_sharing', 'Unshare') }} </NcActionButton> + <NcActionButton v-if="!isEmailShareType && canReshare" + class="new-share-link" + icon="icon-add" + @click.prevent.stop="onNewLinkShare"> + {{ t('files_sharing', 'Add another link') }} + </NcActionButton> </template> <!-- Create new share --> @@ -192,40 +283,39 @@ import { Type as ShareTypes } from '@nextcloud/sharing' 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 NcActionTextEditable from '@nextcloud/vue/dist/Components/NcActionTextEditable.js' import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import Tune from 'vue-material-design-icons/Tune.vue' - -import QuickShareSelect from './SharingEntryQuickShareSelect.vue' - import ExternalShareAction from './ExternalShareAction.vue' +import SharePermissionsEditor from './SharePermissionsEditor.vue' import GeneratePassword from '../utils/GeneratePassword.js' import Share from '../models/Share.js' import SharesMixin from '../mixins/SharesMixin.js' -import ShareDetails from '../mixins/ShareDetails.js' export default { name: 'SharingEntryLink', components: { - ExternalShareAction, NcActions, NcActionButton, + NcActionCheckbox, NcActionInput, NcActionLink, NcActionText, + NcActionTextEditable, NcActionSeparator, NcAvatar, - Tune, - QuickShareSelect, + ExternalShareAction, + SharePermissionsEditor, }, - mixins: [SharesMixin, ShareDetails], + mixins: [SharesMixin], props: { canReshare: { @@ -240,7 +330,6 @@ export default { data() { return { - showDropdown: false, copySuccess: true, copied: false, @@ -504,6 +593,7 @@ export default { canChangeHideDownload() { const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.enabled === false + return this.fileInfo.shareAttributes.some(hasDisabledDownload) }, }, @@ -581,7 +671,7 @@ export default { * accordingly * * @param {Share} share the new share - * @param {boolean} [update] do we update the current share ? + * @param {boolean} [update=false] do we update the current share ? */ async pushNewLinkShare(share, update) { try { @@ -658,6 +748,26 @@ export default { this.loading = false } }, + + /** + * Label changed, let's save it to a different key + * + * @param {string} label the share label + */ + onLabelChange(label) { + this.$set(this.share, 'newLabel', label.trim()) + }, + + /** + * When the note change, we trim, save and dispatch + */ + onLabelSubmit() { + if (typeof this.share.newLabel === 'string') { + this.share.label = this.share.newLabel + this.$delete(this.share, 'newLabel') + this.queueUpdate('label') + } + }, async copyLink() { try { await navigator.clipboard.writeText(this.shareLink) @@ -760,10 +870,6 @@ export default { // YET. We can safely delete the share :) this.$emit('remove:share', this.share) }, - - toggleQuickShareSelect() { - this.showDropdown = !this.showDropdown - }, }, } </script> @@ -773,13 +879,13 @@ export default { display: flex; align-items: center; min-height: 44px; - &__desc { display: flex; flex-direction: column; justify-content: space-between; padding: 8px; line-height: 1.2em; + overflow: hidden; p { color: var(--color-text-maxcontrast); diff --git a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue b/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue deleted file mode 100644 index 8128b3925ee..00000000000 --- a/apps/files_sharing/src/components/SharingEntryQuickShareSelect.vue +++ /dev/null @@ -1,186 +0,0 @@ -<template> - <div :class="{ 'active': showDropdown, 'share-select': true }"> - <span class="trigger-text" @click="toggleDropdown"> - {{ selectedOption }} - <DropdownIcon :size="15" /> - </span> - <div v-if="showDropdown" class="share-select-dropdown-container"> - <div v-for="option in options" - :key="option" - :class="{ 'dropdown-item': true, 'selected': option === selectedOption }" - @click="selectOption(option)"> - {{ option }} - </div> - </div> - </div> -</template> - -<script> -import DropdownIcon from 'vue-material-design-icons/TriangleSmallDown.vue' -import SharesMixin from '../mixins/SharesMixin.js' -import ShareDetails from '../mixins/ShareDetails.js' -import ShareTypes from '../mixins/ShareTypes.js' - -import { - BUNDLED_PERMISSIONS, - ATOMIC_PERMISSIONS, -} from '../lib/SharePermissionsToolBox.js' - -export default { - components: { - DropdownIcon, - }, - mixins: [SharesMixin, ShareDetails, ShareTypes], - props: { - share: { - type: Object, - required: true, - }, - toggle: { - type: Boolean, - default: false, - }, - }, - data() { - return { - selectedOption: '', - showDropdown: this.toggle, - } - }, - computed: { - canViewText() { - return t('files_sharing', 'View only') - }, - canEditText() { - return t('files_sharing', 'Can edit') - }, - fileDropText() { - return t('files_sharing', 'File drop') - }, - customPermissionsText() { - return t('files_sharing', 'Custom permissions') - }, - preSelectedOption() { - // We remove the share permission for the comparison as it is not relevant for bundled permissions. - if ((this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === BUNDLED_PERMISSIONS.READ_ONLY) { - return this.canViewText - } else if (this.share.permissions === BUNDLED_PERMISSIONS.ALL || this.share.permissions === BUNDLED_PERMISSIONS.ALL_FILE) { - return this.canEditText - } else if ((this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === BUNDLED_PERMISSIONS.FILE_DROP) { - return this.fileDropText - } - - return this.customPermissionsText - - }, - options() { - const options = [this.canViewText, this.canEditText] - if (this.supportsFileDrop) { - options.push(this.fileDropText) - } - options.push(this.customPermissionsText) - - return options - }, - supportsFileDrop() { - if (this.isFolder) { - const shareType = this.share.type ?? this.share.shareType - return [this.SHARE_TYPES.SHARE_TYPE_LINK, this.SHARE_TYPES.SHARE_TYPE_EMAIL].includes(shareType) - } - return false - }, - dropDownPermissionValue() { - switch (this.selectedOption) { - case this.canEditText: - return this.isFolder ? BUNDLED_PERMISSIONS.ALL : BUNDLED_PERMISSIONS.ALL_FILE - case this.fileDropText: - return BUNDLED_PERMISSIONS.FILE_DROP - case this.customPermissionsText: - return 'custom' - case this.canViewText: - default: - return BUNDLED_PERMISSIONS.READ_ONLY - } - }, - }, - watch: { - toggle(toggleValue) { - this.showDropdown = toggleValue - }, - }, - mounted() { - this.initializeComponent() - }, - methods: { - toggleDropdown() { - this.showDropdown = !this.showDropdown - }, - selectOption(option) { - this.selectedOption = option - if (option === this.customPermissionsText) { - this.$emit('open-sharing-details') - } else { - this.share.permissions = this.dropDownPermissionValue - this.queueUpdate('permissions') - } - this.showDropdown = false - }, - initializeComponent() { - this.selectedOption = this.preSelectedOption - }, - }, - -} -</script> - -<style lang="scss" scoped> -.share-select { - position: relative; - cursor: pointer; - - .trigger-text { - display: flex; - flex-direction: row; - align-items: center; - font-size: 12.5px; - gap: 2px; - color: var(--color-primary-element); - } - - .share-select-dropdown-container { - position: absolute; - top: 100%; - left: 0; - background-color: var(--color-main-background); - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - padding: 4px 0; - z-index: 1; - - .dropdown-item { - padding: 8px; - font-size: 12px; - - &:hover { - background-color: #f2f2f2; - } - - &.selected { - background-color: #f0f0f0; - } - } - } - - /* Optional: Add a transition effect for smoother dropdown animation */ - .share-select-dropdown-container { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease; - } - - &.active .share-select-dropdown-container { - max-height: 200px; - /* Adjust the value to your desired height */ - } -} -</style> diff --git a/apps/files_sharing/src/components/SharingEntrySimple.vue b/apps/files_sharing/src/components/SharingEntrySimple.vue index 5e858de990b..daff947fe80 100644 --- a/apps/files_sharing/src/components/SharingEntrySimple.vue +++ b/apps/files_sharing/src/components/SharingEntrySimple.vue @@ -29,8 +29,8 @@ {{ subtitle }} </p> </div> - <NcActions v-if="$slots['default']" - ref="actionsComponent" + <NcActions ref="actionsComponent" + v-if="$slots['default']" class="sharing-entry__actions" menu-align="right" :aria-expanded="ariaExpandedValue"> diff --git a/apps/files_sharing/src/components/SharingInput.vue b/apps/files_sharing/src/components/SharingInput.vue index c5ed27477b6..8b740c1bac3 100644 --- a/apps/files_sharing/src/components/SharingInput.vue +++ b/apps/files_sharing/src/components/SharingInput.vue @@ -24,7 +24,6 @@ <div class="sharing-search"> <label for="sharing-search-input">{{ t('files_sharing', 'Search for share recipients') }}</label> <NcSelect ref="select" - v-model="value" input-id="sharing-search-input" class="sharing-search__input" :disabled="!canReshare" @@ -34,9 +33,10 @@ :clear-search-on-blur="() => false" :user-select="true" :options="options" + v-model="value" @open="handleOpen" @search="asyncFind" - @option:selected="openSharingDetails"> + @option:selected="addShare"> <template #no-options="{ search }"> {{ search ? noResultText : t('files_sharing', 'No recommendations. Start typing.') }} </template> @@ -57,7 +57,6 @@ import GeneratePassword from '../utils/GeneratePassword.js' import Share from '../models/Share.js' import ShareRequests from '../mixins/ShareRequests.js' import ShareTypes from '../mixins/ShareTypes.js' -import ShareDetails from '../mixins/ShareDetails.js' export default { name: 'SharingInput', @@ -66,7 +65,7 @@ export default { NcSelect, }, - mixins: [ShareTypes, ShareRequests, ShareDetails], + mixins: [ShareTypes, ShareRequests], props: { shares: { @@ -177,7 +176,7 @@ export default { * Get suggestions * * @param {string} search the search query - * @param {boolean} [lookup] search on lookup server + * @param {boolean} [lookup=false] search on lookup server */ async getSuggestions(search, lookup = false) { this.loading = true @@ -453,6 +452,7 @@ export default { } return { + id: `${result.value.shareType}-${result.value.shareWith}`, shareWith: result.value.shareWith, shareType: result.value.shareType, user: result.uuid || result.value.shareWith, diff --git a/apps/files_sharing/src/lib/SharePermissionsToolBox.js b/apps/files_sharing/src/lib/SharePermissionsToolBox.js index d86f8827b2c..f5806df70bf 100644 --- a/apps/files_sharing/src/lib/SharePermissionsToolBox.js +++ b/apps/files_sharing/src/lib/SharePermissionsToolBox.js @@ -34,7 +34,6 @@ export const BUNDLED_PERMISSIONS = { UPLOAD_AND_UPDATE: ATOMIC_PERMISSIONS.READ | ATOMIC_PERMISSIONS.UPDATE | ATOMIC_PERMISSIONS.CREATE | ATOMIC_PERMISSIONS.DELETE, FILE_DROP: ATOMIC_PERMISSIONS.CREATE, ALL: ATOMIC_PERMISSIONS.UPDATE | ATOMIC_PERMISSIONS.CREATE | ATOMIC_PERMISSIONS.READ | ATOMIC_PERMISSIONS.DELETE | ATOMIC_PERMISSIONS.SHARE, - ALL_FILE: ATOMIC_PERMISSIONS.UPDATE | ATOMIC_PERMISSIONS.READ | ATOMIC_PERMISSIONS.SHARE, } /** diff --git a/apps/files_sharing/src/mixins/ShareDetails.js b/apps/files_sharing/src/mixins/ShareDetails.js deleted file mode 100644 index 53ec8bfe16a..00000000000 --- a/apps/files_sharing/src/mixins/ShareDetails.js +++ /dev/null @@ -1,43 +0,0 @@ -import Share from '../models/Share.js' - -export default { - methods: { - openSharingDetails(share) { - const shareRequestObject = { - fileInfo: this.fileInfo, - share: this.mapShareRequestToShareObject(share), - } - this.$emit('open-sharing-details', shareRequestObject) - }, - openShareDetailsForCustomSettings(share) { - share.setCustomPermissions = true - this.openSharingDetails(share) - }, - mapShareRequestToShareObject(shareRequestObject) { - - if (shareRequestObject.id) { - return shareRequestObject - } - - const share = { - attributes: [ - { - enabled: true, - key: 'download', - scope: 'permissions', - }, - ], - share_type: shareRequestObject.shareType, - share_with: shareRequestObject.shareWith, - is_no_user: shareRequestObject.isNoUser, - user: shareRequestObject.shareWith, - share_with_displayname: shareRequestObject.displayName, - subtitle: shareRequestObject.subtitle, - permissions: shareRequestObject.permissions, - expiration: '', - } - - return new Share(share) - }, - }, -} diff --git a/apps/files_sharing/src/mixins/ShareRequests.js b/apps/files_sharing/src/mixins/ShareRequests.js index 5f301108f6f..e1d246b7400 100644 --- a/apps/files_sharing/src/mixins/ShareRequests.js +++ b/apps/files_sharing/src/mixins/ShareRequests.js @@ -42,20 +42,19 @@ export default { * @param {string} data.path path to the file/folder which should be shared * @param {number} data.shareType 0 = user; 1 = group; 3 = public link; 6 = federated cloud share * @param {string} data.shareWith user/group id with which the file should be shared (optional for shareType > 1) - * @param {boolean} [data.publicUpload] allow public upload to a public shared folder + * @param {boolean} [data.publicUpload=false] allow public upload to a public shared folder * @param {string} [data.password] password to protect public link Share with - * @param {number} [data.permissions] 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all (default: 31, for public shares: 1) - * @param {boolean} [data.sendPasswordByTalk] send the password via a talk conversation - * @param {string} [data.expireDate] expire the shareautomatically after - * @param {string} [data.label] custom label - * @param {string} [data.attributes] Share attributes encoded as json - * @param data.note + * @param {number} [data.permissions=31] 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all (default: 31, for public shares: 1) + * @param {boolean} [data.sendPasswordByTalk=false] send the password via a talk conversation + * @param {string} [data.expireDate=''] expire the shareautomatically after + * @param {string} [data.label=''] custom label + * @param {string} [data.attributes=null] Share attributes encoded as json * @return {Share} the new share * @throws {Error} */ - async createShare({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, note, attributes }) { + async createShare({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, attributes }) { try { - const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, note, attributes }) + const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, attributes }) if (!request?.data?.ocs) { throw request } @@ -67,7 +66,7 @@ export default { const errorMessage = error?.response?.data?.ocs?.meta?.message OC.Notification.showTemporary( errorMessage ? t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) : t('files_sharing', 'Error creating the share'), - { type: 'error' }, + { type: 'error' } ) throw error } @@ -92,7 +91,7 @@ export default { const errorMessage = error?.response?.data?.ocs?.meta?.message OC.Notification.showTemporary( errorMessage ? t('files_sharing', 'Error deleting the share: {errorMessage}', { errorMessage }) : t('files_sharing', 'Error deleting the share'), - { type: 'error' }, + { type: 'error' } ) throw error } @@ -119,7 +118,7 @@ export default { const errorMessage = error?.response?.data?.ocs?.meta?.message OC.Notification.showTemporary( errorMessage ? t('files_sharing', 'Error updating the share: {errorMessage}', { errorMessage }) : t('files_sharing', 'Error updating the share'), - { type: 'error' }, + { type: 'error' } ) } const message = error.response.data.ocs.meta.message diff --git a/apps/files_sharing/src/mixins/SharesMixin.js b/apps/files_sharing/src/mixins/SharesMixin.js index aba1462248a..a29e1a91b02 100644 --- a/apps/files_sharing/src/mixins/SharesMixin.js +++ b/apps/files_sharing/src/mixins/SharesMixin.js @@ -36,17 +36,13 @@ import SharesRequests from './ShareRequests.js' import ShareTypes from './ShareTypes.js' import Config from '../services/ConfigService.js' -import { - BUNDLED_PERMISSIONS, -} from '../lib/SharePermissionsToolBox.js' - export default { mixins: [SharesRequests, ShareTypes], props: { fileInfo: { type: Object, - default: () => { }, + default: () => {}, required: true, }, share: { @@ -125,24 +121,11 @@ export default { monthFormat: 'MMM', } }, - isFolder() { - return this.fileInfo.type === 'dir' - }, - isPublicShare() { - const shareType = this.share.shareType ?? this.share.type - return [this.SHARE_TYPES.SHARE_TYPE_LINK, this.SHARE_TYPES.SHARE_TYPE_EMAIL].includes(shareType) - }, + isShareOwner() { return this.share && this.share.owner === getCurrentUser().uid }, - hasCustomPermissions() { - const bundledPermissions = [ - BUNDLED_PERMISSIONS.ALL, - BUNDLED_PERMISSIONS.READ_ONLY, - BUNDLED_PERMISSIONS.FILE_DROP, - ] - return !bundledPermissions.includes(this.share.permissions) - }, + }, methods: { @@ -197,7 +180,8 @@ export default { * @param {Date} date */ onExpirationChange(date) { - this.share.expireDate = this.formatDateToString(new Date(date)) + this.share.expireDate = this.formatDateToString(date) + this.queueUpdate('expireDate') }, /** @@ -208,6 +192,7 @@ export default { */ onExpirationDisable() { this.share.expireDate = '' + this.queueUpdate('expireDate') }, /** @@ -350,6 +335,7 @@ export default { } } }, + /** * Debounce queueUpdate to avoid requests spamming * more importantly for text data diff --git a/apps/files_sharing/src/models/Share.js b/apps/files_sharing/src/models/Share.js index 5504c63b345..9b1535184a0 100644 --- a/apps/files_sharing/src/models/Share.js +++ b/apps/files_sharing/src/models/Share.js @@ -579,7 +579,7 @@ export default class Share { for (const i in this._share.attributes) { const attr = this._share.attributes[i] if (attr.scope === attrUpdate.scope && attr.key === attrUpdate.key) { - this._share.attributes.splice(i, 1, attrUpdate) + this._share.attributes[i] = attrUpdate return } } diff --git a/apps/files_sharing/src/utils/SharedWithMe.js b/apps/files_sharing/src/utils/SharedWithMe.js index 34de3f017ef..bd39c765221 100644 --- a/apps/files_sharing/src/utils/SharedWithMe.js +++ b/apps/files_sharing/src/utils/SharedWithMe.js @@ -33,7 +33,7 @@ const shareWithTitle = function(share) { owner: share.ownerDisplayName, }, undefined, - { escape: false }, + { escape: false } ) } else if (share.type === ShareTypes.SHARE_TYPE_CIRCLE) { return t( @@ -44,7 +44,7 @@ const shareWithTitle = function(share) { owner: share.ownerDisplayName, }, undefined, - { escape: false }, + { escape: false } ) } else if (share.type === ShareTypes.SHARE_TYPE_ROOM) { if (share.shareWithDisplayName) { @@ -56,7 +56,7 @@ const shareWithTitle = function(share) { owner: share.ownerDisplayName, }, undefined, - { escape: false }, + { escape: false } ) } else { return t( @@ -66,7 +66,7 @@ const shareWithTitle = function(share) { owner: share.ownerDisplayName, }, undefined, - { escape: false }, + { escape: false } ) } } else { @@ -75,7 +75,7 @@ const shareWithTitle = function(share) { 'Shared with you by {owner}', { owner: share.ownerDisplayName }, undefined, - { escape: false }, + { escape: false } ) } } diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue deleted file mode 100644 index dbc8c1508e4..00000000000 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ /dev/null @@ -1,1053 +0,0 @@ -<template> - <div class="sharingTabDetailsView"> - <div class="sharingTabDetailsView__header"> - <span> - <NcAvatar v-if="isUserShare" - class="sharing-entry__avatar" - :is-no-user="share.shareType !== SHARE_TYPES.SHARE_TYPE_USER" - :user="share.shareWith" - :display-name="share.shareWithDisplayName" - :menu-position="'left'" - :url="share.shareWithAvatar" /> - <component :is="getShareTypeIcon(share.type)" :size="32" /> - </span> - <span> - <h1>{{ title }}</h1> - </span> - </div> - <div class="sharingTabDetailsView__quick-permissions"> - <div> - <NcCheckboxRadioSwitch :button-variant="true" - :checked.sync="sharingPermission" - :value="bundledPermissions.READ_ONLY.toString()" - name="sharing_permission_radio" - type="radio" - button-variant-grouped="vertical" - @update:checked="toggleCustomPermissions"> - {{ t('files_sharing', 'View only') }} - <template #icon> - <ViewIcon :size="20" /> - </template> - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch :button-variant="true" - :checked.sync="sharingPermission" - :value="bundledPermissions.ALL.toString()" - name="sharing_permission_radio" - type="radio" - button-variant-grouped="vertical" - @update:checked="toggleCustomPermissions"> - {{ t('files_sharing', 'Allow upload and editing') }} - <template #icon> - <EditIcon :size="20" /> - </template> - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="allowsFileDrop" - :button-variant="true" - :checked.sync="sharingPermission" - :value="bundledPermissions.FILE_DROP.toString()" - name="sharing_permission_radio" - type="radio" - button-variant-grouped="vertical" - @update:checked="toggleCustomPermissions"> - {{ t('files_sharing', 'File drop') }} - <small>{{ t('files_sharing', 'Upload only') }}</small> - <template #icon> - <UploadIcon :size="20" /> - </template> - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch :button-variant="true" - :checked.sync="sharingPermission" - :value="'custom'" - name="sharing_permission_radio" - type="radio" - button-variant-grouped="vertical" - @update:checked="toggleCustomPermissions"> - {{ t('files_sharing', 'Custom permissions') }} - <small>{{ t('files_sharing', customPermissionsList) }}</small> - <template #icon> - <DotsHorizontalIcon :size="20" /> - </template> - </NcCheckboxRadioSwitch> - </div> - </div> - <div class="sharingTabDetailsView__advanced-control"> - <NcButton type="tertiary" - alignment="end-reverse" - @click="advancedSectionAccordionExpanded = !advancedSectionAccordionExpanded"> - {{ t('files_sharing', 'Advanced settings') }} - <template #icon> - <MenuDownIcon /> - </template> - </NcButton> - </div> - <div v-if="advancedSectionAccordionExpanded" class="sharingTabDetailsView__advanced"> - <section> - <NcInputField v-if="isPublicShare" - :value.sync="share.label" - type="text" - :label="t('file_sharing', 'Share label')" /> - <template v-if="isPublicShare"> - <NcCheckboxRadioSwitch :checked.sync="isPasswordProtected" :disabled="isPasswordEnforced"> - {{ t('file_sharing', 'Set password') }} - </NcCheckboxRadioSwitch> - <NcInputField v-if="isPasswordProtected" - :type="hasUnsavedPassword ? 'text' : 'password'" - :value="hasUnsavedPassword ? share.newPassword : '***************'" - :error="passwordError" - :required="isPasswordEnforced" - :label="t('file_sharing', 'Password')" - @update:value="onPasswordChange" /> - - <!-- Migrate icons and remote -> icon="icon-info"--> - <span v-if="isEmailShareType && passwordExpirationTime" icon="icon-info"> - {{ t('files_sharing', 'Password expires {passwordExpirationTime}', { passwordExpirationTime }) }} - </span> - <span v-else-if="isEmailShareType && passwordExpirationTime !== null" icon="icon-error"> - {{ t('files_sharing', 'Password expired') }} - </span> - </template> - <NcCheckboxRadioSwitch :checked.sync="hasExpirationDate" :disabled="isExpiryDateEnforced"> - {{ isExpiryDateEnforced - ? t('files_sharing', 'Expiration date (enforced)') - : t('files_sharing', 'Set expiration date') }} - </NcCheckboxRadioSwitch> - <NcDateTimePickerNative v-if="hasExpirationDate" - id="share-date-picker" - :value="new Date(share.expireDate)" - :min="dateTomorrow" - :max="dateMaxEnforced" - :hide-label="true" - :disabled="isExpiryDateEnforced" - :placeholder="t('file_sharing', 'Expiration date')" - type="date" - @input="onExpirationChange" /> - <NcCheckboxRadioSwitch v-if="isPublicShare" - :disabled="canChangeHideDownload" - :checked.sync="share.hideDownload" - @update:checked="queueUpdate('hideDownload')"> - {{ t('file_sharing', 'Hide download') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="canTogglePasswordProtectedByTalkAvailable" - :checked.sync="isPasswordProtectedByTalk" - @update:checked="onPasswordProtectedByTalkChange"> - {{ t('file_sharing', 'Video verification') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch :checked.sync="writeNoteToRecipientIsChecked"> - {{ t('file_sharing', 'Note to recipient') }} - </NcCheckboxRadioSwitch> - <template v-if="writeNoteToRecipientIsChecked"> - <textarea :value="share.note" @input="share.note = $event.target.value" /> - </template> - <NcCheckboxRadioSwitch :checked.sync="setCustomPermissions"> - {{ t('file_sharing', 'Custom permissions') }} - </NcCheckboxRadioSwitch> - <section v-if="setCustomPermissions" class="custom-permissions-group"> - <NcCheckboxRadioSwitch :disabled="!allowsFileDrop && share.type === SHARE_TYPES.SHARE_TYPE_LINK" :checked.sync="hasRead"> - {{ t('file_sharing', 'Read') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="isFolder" :disabled="!canSetCreate" :checked.sync="canCreate"> - {{ t('file_sharing', 'Create') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch :disabled="!canSetEdit" :checked.sync="canEdit"> - {{ t('file_sharing', 'Update') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="config.isResharingAllowed && share.type !== SHARE_TYPES.SHARE_TYPE_LINK" :disabled="!canSetReshare" :checked.sync="canReshare"> - {{ t('file_sharing', 'Share') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch v-if="!isPublicShare" :disabled="!canSetDownload" :checked.sync="canDownload"> - {{ t('file_sharing', 'Download') }} - </NcCheckboxRadioSwitch> - <NcCheckboxRadioSwitch :disabled="!canSetDelete" :checked.sync="canDelete"> - {{ t('file_sharing', 'Delete') }} - </NcCheckboxRadioSwitch> - </section> - </section> - </div> - - <div class="sharingTabDetailsView__footer"> - <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 class="button-group"> - <NcButton @click="$emit('close-sharing-details')"> - {{ t('file_sharing', 'Cancel') }} - </NcButton> - <NcButton type="primary" @click="saveShare"> - {{ shareButtonText }} - </NcButton> - </div> - </div> - </div> -</template> - -<script> -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' -import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import NcDatetimePicker from '@nextcloud/vue/dist/Components/NcDatetimePicker.js' -import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js' -import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' -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 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' -import ShareIcon from 'vue-material-design-icons/ShareCircle.vue' -import UserIcon from 'vue-material-design-icons/AccountCircleOutline.vue' -import ViewIcon from 'vue-material-design-icons/Eye.vue' -import UploadIcon from 'vue-material-design-icons/Upload.vue' -import MenuDownIcon from 'vue-material-design-icons/MenuDown.vue' -import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue' - -import GeneratePassword from '../utils/GeneratePassword.js' -import Share from '../models/Share.js' -import ShareRequests from '../mixins/ShareRequests.js' -import ShareTypes from '../mixins/ShareTypes.js' -import SharesMixin from '../mixins/SharesMixin.js' - -import { - ATOMIC_PERMISSIONS, - BUNDLED_PERMISSIONS, - hasPermissions, -} from '../lib/SharePermissionsToolBox.js' - -export default { - name: 'SharingDetailsTab', - components: { - NcAvatar, - NcButton, - NcInputField, - NcDatetimePicker, - NcDateTimePickerNative, - NcCheckboxRadioSwitch, - CloseIcon, - CircleIcon, - EditIcon, - LinkIcon, - GroupIcon, - ShareIcon, - UserIcon, - UploadIcon, - ViewIcon, - MenuDownIcon, - DotsHorizontalIcon, - }, - mixins: [ShareTypes, ShareRequests, SharesMixin], - props: { - shareRequestValue: { - type: Object, - required: false, - }, - fileInfo: { - type: Object, - required: true, - }, - share: { - type: Object, - required: true, - }, - }, - data() { - return { - writeNoteToRecipientIsChecked: false, - sharingPermission: BUNDLED_PERMISSIONS.ALL.toString(), - revertSharingPermission: null, - setCustomPermissions: false, - passwordError: false, - advancedSectionAccordionExpanded: false, - bundledPermissions: BUNDLED_PERMISSIONS, - isFirstComponentLoad: true, - test: false, - } - }, - - computed: { - title() { - let title = t('files_sharing', 'Share with ') - if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_USER) { - title = title + this.share.shareWithDisplayName - } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK) { - title = t('files_sharing', 'Share link') - } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP) { - title += ` (${t('files_sharing', 'group')})` - } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_ROOM) { - title += ` (${t('files_sharing', 'conversation')})` - } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE) { - title += ` (${t('files_sharing', 'remote')})` - } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP) { - title += ` (${t('files_sharing', 'remote group')})` - } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GUEST) { - title += ` (${t('files_sharing', 'guest')})` - } - - return title - }, - /** - * Can the sharee edit the shared file ? - */ - canEdit: { - get() { - return this.share.hasUpdatePermission - }, - set(checked) { - this.updateAtomicPermissions({ isEditChecked: checked }) - }, - }, - /** - * Can the sharee create the shared file ? - */ - canCreate: { - get() { - return this.share.hasCreatePermission - }, - set(checked) { - this.updateAtomicPermissions({ isCreateChecked: checked }) - }, - }, - /** - * Can the sharee delete the shared file ? - */ - canDelete: { - get() { - return this.share.hasDeletePermission - }, - set(checked) { - this.updateAtomicPermissions({ isDeleteChecked: checked }) - }, - }, - /** - * Can the sharee reshare the file ? - */ - canReshare: { - get() { - return this.share.hasSharePermission - }, - set(checked) { - this.updateAtomicPermissions({ isReshareChecked: checked }) - }, - }, - /** - * Can the sharee download files or only view them ? - */ - canDownload: { - get() { - return this.share.hasDownloadPermission - }, - set(checked) { - this.updateAtomicPermissions({ isDownloadChecked: checked }) - }, - }, - /** - * Is this share readable - * Needed for some federated shares that might have been added from file drop links - */ - hasRead: { - get() { - return this.share.hasReadPermission - }, - set(checked) { - this.updateAtomicPermissions({ isReadChecked: checked }) - }, - }, - /** - * Does the current share have an expiration date - * - * @return {boolean} - */ - hasExpirationDate: { - get() { - return !!this.share.expireDate || this.config.isDefaultInternalExpireDateEnforced - }, - set(enabled) { - this.share.expireDate = enabled - ? this.formatDateToString(this.defaultExpiryDate) - : '' - }, - }, - /** - * 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 - this.share.password = enabled ? await GeneratePassword() : '' - this.$set(this.share, 'newPassword', this.share.password) - }, - }, - /** - * Is the current share a folder ? - * - * @return {boolean} - */ - isFolder() { - return this.fileInfo.type === 'dir' - }, - dateMaxEnforced() { - if (!this.isRemote && this.config.isDefaultInternalExpireDateEnforced) { - return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultInternalExpireDate)) - } else if (this.config.isDefaultRemoteExpireDateEnforced) { - return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultRemoteExpireDate)) - } - return null - }, - /** - * @return {boolean} - */ - isSetDownloadButtonVisible() { - // TODO: Implement download permission for circle shares instead of hiding the option. - // https://github.com/nextcloud/server/issues/39161 - if (this.share && this.share.type === this.SHARE_TYPES.SHARE_TYPE_CIRCLE) { - return false - } - - const allowedMimetypes = [ - // Office documents - 'application/msword', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.ms-powerpoint', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/vnd.ms-excel', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.presentation', - ] - - return this.isFolder || allowedMimetypes.includes(this.fileInfo.mimetype) - }, - isPasswordEnforced() { - return this.isPublicShare && this.config.enforcePasswordForPublicLink - }, - isExpiryDateEnforced() { - return this.config.isDefaultInternalExpireDateEnforced - }, - defaultExpiryDate() { - if ((this.isGroupShare || this.isUserShare) && this.config.isDefaultInternalExpireDateEnabled) { - return new Date(this.config.defaultInternalExpirationDate) - } else if (this.isRemoteShare && this.config.isDefaultRemoteExpireDateEnabled) { - return new Date(this.config.defaultRemoteExpireDateEnabled) - } else if (this.isPublicShare && this.config.isDefaultExpireDateEnabled) { - return new Date(this.config.defaultExpirationDate) - } - return new Date(new Date().setDate(new Date().getDate() + 1)) - }, - isUserShare() { - return this.share.type === this.SHARE_TYPES.SHARE_TYPE_USER - }, - isGroupShare() { - return this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP - }, - isRemoteShare() { - return this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP || this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE - }, - isNewShare() { - return this.share.id === null || this.share.id === undefined - }, - allowsFileDrop() { - if (this.isFolder) { - if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK || this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL) { - return true - } - } - return false - }, - hasFileDropPermissions() { - return this.share.permissions === this.bundledPermissions.FILE_DROP - }, - shareButtonText() { - if (this.isNewShare) { - return t('file_sharing', 'Save share') - } - return t('file_sharing', 'Update share') - - }, - /** - * Can the sharer set whether the sharee can edit the file ? - * - * @return {boolean} - */ - canSetEdit() { - // If the owner revoked the permission after the resharer granted it - // the share still has the permission, and the resharer is still - // allowed to revoke it too (but not to grant it again). - return (this.fileInfo.sharePermissions & OC.PERMISSION_UPDATE) || this.canEdit - }, - - /** - * Can the sharer set whether the sharee can create the file ? - * - * @return {boolean} - */ - canSetCreate() { - // If the owner revoked the permission after the resharer granted it - // the share still has the permission, and the resharer is still - // allowed to revoke it too (but not to grant it again). - return (this.fileInfo.sharePermissions & OC.PERMISSION_CREATE) || this.canCreate - }, - - /** - * Can the sharer set whether the sharee can delete the file ? - * - * @return {boolean} - */ - canSetDelete() { - // If the owner revoked the permission after the resharer granted it - // the share still has the permission, and the resharer is still - // allowed to revoke it too (but not to grant it again). - return (this.fileInfo.sharePermissions & OC.PERMISSION_DELETE) || this.canDelete - }, - /** - * Can the sharer set whether the sharee can reshare the file ? - * - * @return {boolean} - */ - canSetReshare() { - // If the owner revoked the permission after the resharer granted it - // the share still has the permission, and the resharer is still - // allowed to revoke it too (but not to grant it again). - return (this.fileInfo.sharePermissions & OC.PERMISSION_SHARE) || this.canReshare - }, - /** - * Can the sharer set whether the sharee can download the file ? - * - * @return {boolean} - */ - canSetDownload() { - // If the owner revoked the permission after the resharer granted it - // the share still has the permission, and the resharer is still - // allowed to revoke it too (but not to grant it again). - return (this.fileInfo.canDownload() || this.canDownload) - }, - // if newPassword exists, but is empty, it means - // the user deleted the original password - hasUnsavedPassword() { - return this.share.newPassword !== undefined - }, - passwordExpirationTime() { - if (this.share.passwordExpirationTime === null) { - return null - } - - const expirationTime = moment(this.share.passwordExpirationTime) - - if (expirationTime.diff(moment()) < 0) { - return false - } - - return expirationTime.fromNow() - }, - - /** - * Is Talk enabled? - * - * @return {boolean} - */ - isTalkEnabled() { - return OC.appswebroots.spreed !== undefined - }, - - /** - * Is it possible to protect the password by Talk? - * - * @return {boolean} - */ - isPasswordProtectedByTalkAvailable() { - return this.isPasswordProtected && this.isTalkEnabled - }, - /** - * Is the current share password protected by Talk? - * - * @return {boolean} - */ - isPasswordProtectedByTalk: { - get() { - return this.share.sendPasswordByTalk - }, - async set(enabled) { - this.share.sendPasswordByTalk = enabled - }, - }, - /** - * Is the current share an email share ? - * - * @return {boolean} - */ - isEmailShareType() { - return this.share - ? this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL - : false - }, - canTogglePasswordProtectedByTalkAvailable() { - if (!this.isPublicShare || !this.isPasswordProtected) { - // Makes no sense - return false - } else if (this.isEmailShareType && !this.hasUnsavedPassword) { - // For email shares we need a new password in order to enable or - // disable - return false - } - - // Anything else should be fine - return true - }, - canChangeHideDownload() { - const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.enabled === false - return this.fileInfo.shareAttributes.some(hasDisabledDownload) - }, - customPermissionsList() { - const perms = [] - if (hasPermissions(this.share.permissions, ATOMIC_PERMISSIONS.READ)) { - perms.push('read') - } - if (hasPermissions(this.share.permissions, ATOMIC_PERMISSIONS.CREATE)) { - perms.push('create') - } - if (hasPermissions(this.share.permissions, ATOMIC_PERMISSIONS.UPDATE)) { - perms.push('update') - } - if (hasPermissions(this.share.permissions, ATOMIC_PERMISSIONS.DELETE)) { - perms.push('delete') - } - if (hasPermissions(this.share.permissions, ATOMIC_PERMISSIONS.SHARE)) { - perms.push('share') - } - if (this.share.hasDownloadPermission) { - perms.push('download') - } - const capitalizeFirstAndJoin = array => array.map((item, index) => index === 0 ? item[0].toUpperCase() + item.substring(1) : item).join(', ') - - return capitalizeFirstAndJoin(perms) - - }, - }, - watch: { - setCustomPermissions(isChecked) { - if (isChecked) { - this.sharingPermission = 'custom' - } else { - this.sharingPermission = this.revertSharingPermission - } - }, - }, - beforeMount() { - this.initializePermissions() - this.initializeAttributes() - console.debug('shareSentIn', this.share) - console.debug('config', this.config) - }, - - methods: { - updateAtomicPermissions({ - isReadChecked = this.hasRead, - isEditChecked = this.canEdit, - isCreateChecked = this.canCreate, - isDeleteChecked = this.canDelete, - isReshareChecked = this.canReshare, - isDownloadChecked = this.canDownload, - } = {}) { - // calc permissions if checked - const permissions = 0 - | (isReadChecked ? ATOMIC_PERMISSIONS.READ : 0) - | (isCreateChecked ? ATOMIC_PERMISSIONS.CREATE : 0) - | (isDeleteChecked ? ATOMIC_PERMISSIONS.DELETE : 0) - | (isEditChecked ? ATOMIC_PERMISSIONS.UPDATE : 0) - | (isReshareChecked ? ATOMIC_PERMISSIONS.SHARE : 0) - this.share.permissions = permissions - if (this.share.hasDownloadPermission !== isDownloadChecked) { - this.$set(this.share, 'hasDownloadPermission', isDownloadChecked) - } - }, - - toggleCustomPermissions(selectedPermission) { - if (this.sharingPermission === 'custom') { - this.advancedSectionAccordionExpanded = true - this.setCustomPermissions = true - } else { - this.advancedSectionAccordionExpanded = false - this.revertSharingPermission = selectedPermission - this.setCustomPermissions = false - } - }, - initializeAttributes() { - - if (this.isNewShare) return - - let hasAdvancedAttributes = false - if (this.isValidShareAttribute(this.share.note)) { - this.writeNoteToRecipientIsChecked = true - hasAdvancedAttributes = true - } - - if (this.isValidShareAttribute(this.share.password)) { - hasAdvancedAttributes = true - } - - if (this.isValidShareAttribute(this.share.expireDate)) { - hasAdvancedAttributes = true - } - - if (this.isValidShareAttribute(this.share.label)) { - hasAdvancedAttributes = true - } - - if (hasAdvancedAttributes) { - this.advancedSectionAccordionExpanded = true - } - - }, - initializePermissions() { - if (this.share.share_type) { - this.share.type = this.share.share_type - } - // shareType 0 (USER_SHARE) would evaluate to zero - // Hence the use of hasOwnProperty - if ('shareType' in this.share) { - this.share.type = this.share.shareType - } - if (this.isNewShare) { - if (this.isPublicShare) { - this.sharingPermission = BUNDLED_PERMISSIONS.READ_ONLY.toString() - } else { - this.sharingPermission = BUNDLED_PERMISSIONS.ALL.toString() - } - - } else { - if (this.hasCustomPermissions || this.share.setCustomPermissions) { - this.sharingPermission = 'custom' - this.advancedSectionAccordionExpanded = true - this.setCustomPermissions = true - } else { - this.sharingPermission = this.share.permissions.toString() - } - } - }, - async saveShare() { - const permissionsAndAttributes = ['permissions', 'attributes', 'note', 'expireDate'] - const publicShareAttributes = ['label', 'password', 'hideDownload'] - if (this.isPublicShare) { - permissionsAndAttributes.push(...publicShareAttributes) - } - const sharePermissionsSet = parseInt(this.sharingPermission) - if (this.setCustomPermissions) { - this.updateAtomicPermissions() - } else { - this.share.permissions = sharePermissionsSet - } - - if (!this.isFolder && this.share.permissions === BUNDLED_PERMISSIONS.ALL) { - // It's not possible to create an existing file. - this.share.permissions = BUNDLED_PERMISSIONS.ALL_FILE - } - if (!this.writeNoteToRecipientIsChecked) { - this.share.note = '' - } - - if (this.isPasswordProtected) { - if (this.isValidShareAttribute(this.share.newPassword)) { - this.share.password = this.share.newPassword - this.$delete(this.share, 'newPassword') - } else { - if (this.isPasswordEnforced) { - this.passwordError = true - return - } - } - } else { - this.share.password = '' - } - - if (!this.hasExpirationDate) { - this.share.expireDate = '' - } - - if (this.isNewShare) { - const incomingShare = { - permissions: this.share.permissions, - shareType: this.share.type, - shareWith: this.share.shareWith, - attributes: this.share.attributes, - note: this.share.note, - } - - if (this.hasExpirationDate) { - incomingShare.expireDate = this.share.expireDate - } - - if (this.isPasswordProtected) { - incomingShare.password = this.share.password - } - - const share = await this.addShare(incomingShare, this.fileInfo, this.config) - this.share = share - this.$emit('add:share', this.share) - } else { - this.queueUpdate(...permissionsAndAttributes) - } - - this.$emit('close-sharing-details') - }, - /** - * Process the new share request - * - * @param {object} value the multiselect option - * @param {object} fileInfo file data - * @param {Config} config instance configs - */ - async addShare(value, fileInfo, config) { - // Clear the displayed selection - this.value = null - - // handle externalResults from OCA.Sharing.ShareSearch - if (value.handler) { - const share = await value.handler(this) - this.$emit('add:share', new Share(share)) - return true - } - - // this.loading = true // Are we adding loaders the new share flow? - console.debug('Adding a new share from the input for', value) - try { - const path = (fileInfo.path + '/' + fileInfo.name).replace('//', '/') - const share = await this.createShare({ - path, - shareType: value.shareType, - shareWith: value.shareWith, - permissions: value.permissions, - attributes: JSON.stringify(fileInfo.shareAttributes), - ...(value.note ? { note: value.note } : {}), - ...(value.password ? { password: value.password } : {}), - ...(value.expireDate ? { expireDate: value.expireDate } : {}), - }) - return share - } catch (error) { - console.error('Error while adding new share', error) - } finally { - // this.loading = false // No loader here yet - } - }, - async removeShare() { - await this.onDelete() - this.$emit('close-sharing-details') - }, - /** - * Update newPassword values - * of share. If password is set but not newPassword - * then the user did not changed the password - * If both co-exists, the password have changed and - * we show it in plain text. - * Then on submit (or menu close), we sync it. - * - * @param {string} password the changed password - */ - onPasswordChange(password) { - this.passwordError = !this.isValidShareAttribute(password) - this.$set(this.share, 'newPassword', password) - }, - /** - * Update the password along with "sendPasswordByTalk". - * - * If the password was modified the new password is sent; otherwise - * updating a mail share would fail, as in that case it is required that - * a new password is set when enabling or disabling - * "sendPasswordByTalk". - */ - onPasswordProtectedByTalkChange() { - if (this.hasUnsavedPassword) { - this.share.password = this.share.newPassword.trim() - } - - this.queueUpdate('sendPasswordByTalk', 'password') - }, - isValidShareAttribute(value) { - if ([null, undefined].includes(value)) { - return false - } - - if (!(value.trim().length > 0)) { - return false - } - - return true - }, - getShareTypeIcon(type) { - switch (type) { - case this.SHARE_TYPES.SHARE_TYPE_LINK: - return LinkIcon - case this.SHARE_TYPES.SHARE_TYPE_GUEST: - return UserIcon - case this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP: - case this.SHARE_TYPES.SHARE_TYPE_GROUP: - return GroupIcon - case this.SHARE_TYPES.SHARE_TYPE_EMAIL: - return EmailIcon - case this.SHARE_TYPES.SHARE_TYPE_CIRCLE: - return CircleIcon - case this.SHARE_TYPES.SHARE_TYPE_ROOM: - return ShareIcon - case this.SHARE_TYPES.SHARE_TYPE_DECK: - return ShareIcon - case this.SHARE_TYPES.SHARE_TYPE_SCIENCEMESH: - return ShareIcon - default: - return null // Or a default icon component if needed - } - }, - }, -} -</script> - -<style lang="scss" scoped> -.sharingTabDetailsView { - display: flex; - flex-direction: column; - align-items: flex-start; - width: 96%; - margin: 0 auto; - - &__header { - display: flex; - align-items: center; - box-sizing: border-box; - margin: 0.2em; - - span { - display: flex; - align-items: center; - - h1 { - font-size: 15px; - padding-left: 0.3em; - } - - } - } - - &__quick-permissions { - display: flex; - justify-content: center; - margin-bottom: 0.2em; - width: 100%; - margin: 0 auto; - border-radius: 0; - - div { - width: 100%; - - span { - width: 100%; - - span:nth-child(1) { - align-items: center; - justify-content: center; - color: var(--color-primary-element); - padding: 0.1em; - } - - ::v-deep label { - - span { - display: flex; - flex-direction: column; - } - } - } - - } - } - - &__advanced-control { - width: 100%; - - button { - margin-top: 0.5em; - } - - } - - &__advanced { - width: 100%; - margin-bottom: 0.5em; - text-align: left; - padding-left: 0; - - section { - - textarea, - div.mx-datepicker { - width: 100%; - } - - textarea { - height: 80px; - } - - /* - 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; - } - } - - section.custom-permissions-group { - padding-left: 1.5em; - } - } - } - - &__footer { - width: 100%; - display: flex; - position: sticky; - bottom: 0; - flex-direction: column; - justify-content: space-between; - align-items: flex-start; - - >button:first-child { - font-size: 12px; - color: rgb(223, 7, 7); - background-color: #f5f5f5; - } - - .button-group { - display: flex; - justify-content: space-between; - width: 100%; - margin-top: 16px; - - button { - margin-left: 16px; - - &:first-child { - margin-left: 0; - } - } - } - } -} -</style> diff --git a/apps/files_sharing/src/views/SharingLinkList.vue b/apps/files_sharing/src/views/SharingLinkList.vue index 899424be1d8..c3f1425cb70 100644 --- a/apps/files_sharing/src/views/SharingLinkList.vue +++ b/apps/files_sharing/src/views/SharingLinkList.vue @@ -39,8 +39,7 @@ :file-info="fileInfo" @add:share="addShare(...arguments)" @update:share="awaitForShare(...arguments)" - @remove:share="removeShare" - @open-sharing-details="openSharingDetails(share)" /> + @remove:share="removeShare" /> </template> </ul> </template> @@ -50,7 +49,6 @@ import Share from '../models/Share.js' import ShareTypes from '../mixins/ShareTypes.js' import SharingEntryLink from '../components/SharingEntryLink.vue' -import ShareDetails from '../mixins/ShareDetails.js' export default { name: 'SharingLinkList', @@ -59,7 +57,7 @@ export default { SharingEntryLink, }, - mixins: [ShareTypes, ShareDetails], + mixins: [ShareTypes], props: { fileInfo: { diff --git a/apps/files_sharing/src/views/SharingList.vue b/apps/files_sharing/src/views/SharingList.vue index 6da48c2eece..05dc87d9b07 100644 --- a/apps/files_sharing/src/views/SharingList.vue +++ b/apps/files_sharing/src/views/SharingList.vue @@ -27,15 +27,15 @@ :file-info="fileInfo" :share="share" :is-unique="isUnique(share)" - @open-sharing-details="openSharingDetails(share)" /> + @remove:share="removeShare" /> </ul> </template> <script> // eslint-disable-next-line no-unused-vars +import Share from '../models/Share.js' import SharingEntry from '../components/SharingEntry.vue' import ShareTypes from '../mixins/ShareTypes.js' -import ShareDetails from '../mixins/ShareDetails.js' export default { name: 'SharingList', @@ -44,12 +44,12 @@ export default { SharingEntry, }, - mixins: [ShareTypes, ShareDetails], + mixins: [ShareTypes], props: { fileInfo: { type: Object, - default: () => { }, + default: () => {}, required: true, }, shares: { @@ -58,6 +58,7 @@ export default { required: true, }, }, + computed: { hasShares() { return this.shares.length === 0 @@ -70,5 +71,18 @@ export default { } }, }, + + methods: { + /** + * Remove a share from the shares list + * + * @param {Share} share the share to remove + */ + removeShare(share) { + const index = this.shares.findIndex(item => item === share) + // eslint-disable-next-line vue/no-mutating-props + this.shares.splice(index, 1) + }, + }, } </script> diff --git a/apps/files_sharing/src/views/SharingTab.vue b/apps/files_sharing/src/views/SharingTab.vue index e5d26156750..bfaf8a766ee 100644 --- a/apps/files_sharing/src/views/SharingTab.vue +++ b/apps/files_sharing/src/views/SharingTab.vue @@ -29,7 +29,7 @@ </div> <!-- shares content --> - <div v-if="!showSharingDetailsView" class="sharingTab__content"> + <div v-else class="sharingTab__content"> <!-- shared with me information --> <SharingEntrySimple v-if="isSharedWithMe" v-bind="sharedWithMe" class="sharing-entry__reshare"> <template #avatar> @@ -46,22 +46,20 @@ :link-shares="linkShares" :reshare="reshare" :shares="shares" - @open-sharing-details="toggleShareDetailsView" /> + @add:share="addShare" /> <!-- link shares list --> <SharingLinkList v-if="!loading" ref="linkShareList" :can-reshare="canReshare" :file-info="fileInfo" - :shares="linkShares" - @open-sharing-details="toggleShareDetailsView" /> + :shares="linkShares" /> <!-- other shares list --> <SharingList v-if="!loading" ref="shareList" :shares="shares" - :file-info="fileInfo" - @open-sharing-details="toggleShareDetailsView" /> + :file-info="fileInfo" /> <!-- inherited shares --> <SharingInherited v-if="canReshare && !loading" :file-info="fileInfo" /> @@ -76,15 +74,6 @@ :name="fileInfo.name" /> </div> - <!-- share details --> - <div v-else> - <SharingDetailsTab :file-info="shareDetailsData.fileInfo" - :share="shareDetailsData.share" - @close-sharing-details="toggleShareDetailsView" - @add:share="addShare" - @remove:share="removeShare" /> - </div> - <!-- additional entries, use it with cautious --> <div v-for="(section, index) in sections" :ref="'section-' + index" @@ -113,7 +102,6 @@ import SharingInput from '../components/SharingInput.vue' import SharingInherited from './SharingInherited.vue' import SharingLinkList from './SharingLinkList.vue' import SharingList from './SharingList.vue' -import SharingDetailsTab from './SharingDetailsTab.vue' export default { name: 'SharingTab', @@ -127,7 +115,6 @@ export default { SharingInput, SharingLinkList, SharingList, - SharingDetailsTab, }, mixins: [ShareTypes], @@ -135,7 +122,7 @@ export default { data() { return { config: new Config(), - deleteEvent: null, + error: '', expirationInterval: null, loading: true, @@ -150,8 +137,6 @@ export default { sections: OCA.Sharing.ShareTabSections.getSections(), projectsEnabled: loadState('core', 'projects_enabled', false), - showSharingDetailsView: false, - shareDetailsData: {}, } }, @@ -240,8 +225,6 @@ export default { this.sharedWithMe = {} this.shares = [] this.linkShares = [] - this.showSharingDetailsView = false - this.shareDetailsData = {} }, /** @@ -324,7 +307,7 @@ export default { 'Shared with you by {owner}', { owner: this.fileInfo.shareOwner }, undefined, - { escape: false }, + { escape: false } ), user: this.fileInfo.shareOwnerId, } @@ -338,7 +321,7 @@ export default { * @param {Share} share the share to add to the array * @param {Function} [resolve] a function to run after the share is added and its component initialized */ - addShare(share, resolve = () => { }) { + addShare(share, resolve = () => {}) { // only catching share type MAIL as link shares are added differently // meaning: not from the ShareInput if (share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL) { @@ -348,16 +331,7 @@ export default { } this.awaitForShare(share, resolve) }, - /** - * Remove a share from the shares list - * - * @param {Share} share the share to remove - */ - removeShare(share) { - const index = this.shares.findIndex(item => item.id === share.id) - // eslint-disable-next-line vue/no-mutating-props - this.shares.splice(index, 1) - }, + /** * Await for next tick and render after the list updated * Then resolve with the matched vue component of the @@ -381,12 +355,6 @@ export default { } }) }, - toggleShareDetailsView(eventData) { - if (eventData) { - this.shareDetailsData = eventData - } - this.showSharingDetailsView = !this.showSharingDetailsView - }, }, } </script> @@ -400,7 +368,6 @@ export default { &__content { padding: 0 6px; } - &__additionalContent { margin: 44px 0; } |