diff options
Diffstat (limited to 'apps/files_sharing/src/views')
-rw-r--r-- | apps/files_sharing/src/views/CollaborationView.vue | 36 | ||||
-rw-r--r-- | apps/files_sharing/src/views/FilesHeaderNoteToRecipient.vue | 6 | ||||
-rw-r--r-- | apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue | 95 | ||||
-rw-r--r-- | apps/files_sharing/src/views/PublicAuthPrompt.vue | 123 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingDetailsTab.vue | 111 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingLinkList.vue | 12 | ||||
-rw-r--r-- | apps/files_sharing/src/views/SharingTab.vue | 108 |
7 files changed, 237 insertions, 254 deletions
diff --git a/apps/files_sharing/src/views/CollaborationView.vue b/apps/files_sharing/src/views/CollaborationView.vue deleted file mode 100644 index b75ad53e1b8..00000000000 --- a/apps/files_sharing/src/views/CollaborationView.vue +++ /dev/null @@ -1,36 +0,0 @@ -<!-- - - SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - - SPDX-License-Identifier: AGPL-3.0-or-later ---> - -<template> - <CollectionList v-if="fileId" - :id="fileId" - type="file" - :name="filename" /> -</template> - -<script> -import { CollectionList } from 'nextcloud-vue-collections' - -export default { - name: 'CollaborationView', - components: { - CollectionList, - }, - computed: { - fileId() { - if (this.$root.model && this.$root.model.id) { - return '' + this.$root.model.id - } - return null - }, - filename() { - if (this.$root.model && this.$root.model.name) { - return '' + this.$root.model.name - } - return '' - }, - }, -} -</script> diff --git a/apps/files_sharing/src/views/FilesHeaderNoteToRecipient.vue b/apps/files_sharing/src/views/FilesHeaderNoteToRecipient.vue index 31b66741698..ec6348606fb 100644 --- a/apps/files_sharing/src/views/FilesHeaderNoteToRecipient.vue +++ b/apps/files_sharing/src/views/FilesHeaderNoteToRecipient.vue @@ -6,7 +6,7 @@ <NcNoteCard v-if="note.length > 0" class="note-to-recipient" type="info"> - <p v-if="user" class="note-to-recipient__heading"> + <p v-if="displayName" class="note-to-recipient__heading"> {{ t('files_sharing', 'Note from') }} <NcUserBubble :user="user.id" :display-name="user.displayName" /> </p> @@ -28,13 +28,13 @@ import NcUserBubble from '@nextcloud/vue/components/NcUserBubble' const folder = ref<Folder>() const note = computed<string>(() => folder.value?.attributes.note ?? '') +const displayName = computed<string>(() => folder.value?.attributes['owner-display-name'] ?? '') const user = computed(() => { const id = folder.value?.owner - const displayName = folder.value?.attributes?.['owner-display-name'] if (id !== getCurrentUser()?.uid) { return { id, - displayName, + displayName: displayName.value, } } return null diff --git a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue index 5571e5e9f5d..dac22748d8a 100644 --- a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue +++ b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue @@ -5,13 +5,29 @@ <template> <NcEmptyContent class="file-drop-empty-content" data-cy-files-sharing-file-drop - :name="t('files_sharing', 'File drop')"> + :name="name"> <template #icon> <NcIconSvgWrapper :svg="svgCloudUpload" /> </template> <template #description> - {{ t('files_sharing', 'Upload files to {foldername}.', { foldername }) }} - {{ disclaimer === '' ? '' : t('files_sharing', 'By uploading files, you agree to the terms of service.') }} + <p> + {{ shareNote || t('files_sharing', 'Upload files to {foldername}.', { foldername }) }} + </p> + <p v-if="disclaimer"> + {{ t('files_sharing', 'By uploading files, you agree to the terms of service.') }} + </p> + <NcNoteCard v-if="getSortedUploads().length" + class="file-drop-empty-content__note-card" + type="success"> + <h2 id="file-drop-empty-content__heading"> + {{ t('files_sharing', 'Successfully uploaded files') }} + </h2> + <ul aria-labelledby="file-drop-empty-content__heading" class="file-drop-empty-content__list"> + <li v-for="file in getSortedUploads()" :key="file"> + {{ file }} + </li> + </ul> + </NcNoteCard> </template> <template #action> <template v-if="disclaimer"> @@ -34,34 +50,87 @@ </NcEmptyContent> </template> +<script lang="ts"> +/* eslint-disable import/first */ + +// We need this on module level rather than on the instance as view will be refreshed by the files app after uploading +const uploads = new Set<string>() +</script> + <script setup lang="ts"> import { loadState } from '@nextcloud/initial-state' import { translate as t } from '@nextcloud/l10n' -import { getUploader, UploadPicker } from '@nextcloud/upload' +import { getUploader, UploadPicker, UploadStatus } from '@nextcloud/upload' import { ref } from 'vue' import NcButton from '@nextcloud/vue/components/NcButton' import NcDialog from '@nextcloud/vue/components/NcDialog' import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' -import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw' +import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' +import svgCloudUpload from '@mdi/svg/svg/cloud-upload-outline.svg?raw' defineProps<{ foldername: string }>() const disclaimer = loadState<string>('files_sharing', 'disclaimer', '') +const shareLabel = loadState<string>('files_sharing', 'label', '') +const shareNote = loadState<string>('files_sharing', 'note', '') + +const name = shareLabel || t('files_sharing', 'File drop') + const showDialog = ref(false) const uploadDestination = getUploader().destination -</script> -<style scoped> -:deep(.terms-of-service-dialog) { - min-height: min(100px, 20vh); +getUploader() + .addNotifier((upload) => { + if (upload.status === UploadStatus.FINISHED && upload.file.name) { + // if a upload is finished and is not a meta upload (name is set) + // then we add the upload to the list of finished uploads to be shown to the user + uploads.add(upload.file.name) + } + }) + +/** + * Get the previous uploads as sorted list + */ +function getSortedUploads() { + return [...uploads].sort((a, b) => a.localeCompare(b)) } -/* TODO fix in library */ -.file-drop-empty-content :deep(.empty-content__action) { - display: flex; - gap: var(--default-grid-baseline); +</script> + +<style scoped lang="scss"> +.file-drop-empty-content { + margin: auto; + max-width: max(50vw, 300px); + + .file-drop-empty-content__note-card { + width: fit-content; + margin-inline: auto; + } + + #file-drop-empty-content__heading { + margin-block: 0 10px; + font-weight: bold; + font-size: 20px; + } + + .file-drop-empty-content__list { + list-style: inside; + max-height: min(350px, 33vh); + overflow-y: scroll; + padding-inline-end: calc(2 * var(--default-grid-baseline)); + } + + :deep(.terms-of-service-dialog) { + min-height: min(100px, 20vh); + } + + /* TODO fix in library */ + :deep(.empty-content__action) { + display: flex; + gap: var(--default-grid-baseline); + } } </style> diff --git a/apps/files_sharing/src/views/PublicAuthPrompt.vue b/apps/files_sharing/src/views/PublicAuthPrompt.vue deleted file mode 100644 index afa1e10ac56..00000000000 --- a/apps/files_sharing/src/views/PublicAuthPrompt.vue +++ /dev/null @@ -1,123 +0,0 @@ -<!-- - - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - - SPDX-License-Identifier: AGPL-3.0-or-later ---> - -<template> - <NcDialog :buttons="dialogButtons" - class="public-auth-prompt" - data-cy-public-auth-prompt-dialog - is-form - :can-close="false" - :name="dialogName" - @submit="$emit('close', name)"> - <p v-if="owner" class="public-auth-prompt__subtitle"> - {{ t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) }} - </p> - - <!-- Header --> - <NcNoteCard class="public-auth-prompt__header" - :text="t('files_sharing', 'To upload files, you need to provide your name first.')" - type="info" /> - - <!-- Form --> - <NcTextField ref="input" - class="public-auth-prompt__input" - data-cy-public-auth-prompt-dialog-name - :label="t('files_sharing', 'Nickname')" - :placeholder="t('files_sharing', 'Enter your nickname')" - minlength="2" - name="name" - required - :value.sync="name" /> - </NcDialog> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue' -import { t } from '@nextcloud/l10n' - -import NcDialog from '@nextcloud/vue/components/NcDialog' -import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' -import NcTextField from '@nextcloud/vue/components/NcTextField' -import { loadState } from '@nextcloud/initial-state' - -export default defineComponent({ - name: 'PublicAuthPrompt', - - components: { - NcDialog, - NcNoteCard, - NcTextField, - }, - - props: { - /** - * Preselected nickname - * @default '' No name preselected by default - */ - nickname: { - type: String, - default: '', - }, - }, - - setup() { - return { - t, - - owner: loadState('files_sharing', 'owner', ''), - ownerDisplayName: loadState('files_sharing', 'ownerDisplayName', ''), - label: loadState('files_sharing', 'label', ''), - note: loadState('files_sharing', 'note', ''), - filename: loadState('files_sharing', 'filename', ''), - } - }, - - data() { - return { - name: '', - } - }, - - computed: { - dialogName() { - return this.t('files_sharing', 'Upload files to {folder}', { folder: this.label || this.filename }) - }, - dialogButtons() { - return [{ - label: t('files_sharing', 'Submit name'), - type: 'primary', - nativeType: 'submit', - }] - }, - }, - - watch: { - /** Reset name to pre-selected nickname (e.g. Talk / Collabora ) */ - nickname: { - handler() { - this.name = this.nickname - }, - immediate: true, - }, - }, -}) -</script> -<style scoped lang="scss"> -.public-auth-prompt { - &__subtitle { - // Smaller than dialog title - font-size: 1.25em; - margin-block: 0 calc(3 * var(--default-grid-baseline)); - } - - &__header { - margin-block: 0 calc(3 * var(--default-grid-baseline)); - } - - &__input { - margin-block: calc(4 * var(--default-grid-baseline)) calc(2 * var(--default-grid-baseline)); - } -} -</style> diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index 659d65cd70c..b3a3b95d92e 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -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" @@ -128,7 +128,7 @@ </NcCheckboxRadioSwitch> <NcPasswordField v-if="isPasswordProtected" autocomplete="new-password" - :value="hasUnsavedPassword ? share.newPassword : ''" + :value="share.newPassword ?? ''" :error="passwordError" :helper-text="errorPasswordLabel || passwordHint" :required="isPasswordEnforced && isNewShare" @@ -226,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> @@ -249,8 +236,22 @@ @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> @@ -280,7 +281,7 @@ 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' @@ -372,7 +373,7 @@ export default { title() { switch (this.share.type) { case ShareType.User: - return t('files_sharing', 'Share with {userName}', { userName: this.share.shareWithDisplayName }) + 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 ShareType.Link: @@ -383,6 +384,9 @@ export default { return t('files_sharing', 'Share in conversation') 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 ShareType.RemoteGroup: @@ -399,6 +403,9 @@ export default { } } }, + allPermissions() { + return this.isFolder ? this.bundledPermissions.ALL.toString() : this.bundledPermissions.ALL_FILE.toString() + }, /** * Can the sharee edit the shared file ? */ @@ -496,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(true) - 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} @@ -731,8 +718,15 @@ export default { [ATOMIC_PERMISSIONS.DELETE]: this.t('files_sharing', 'Delete'), } - return [ATOMIC_PERMISSIONS.READ, ATOMIC_PERMISSIONS.CREATE, ATOMIC_PERMISSIONS.UPDATE, ...(this.resharingIsPossible ? [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())) @@ -850,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) @@ -872,7 +873,7 @@ export default { async initializeAttributes() { if (this.isNewShare) { - if (this.isPasswordEnforced && this.isPublicShare) { + if ((this.config.enableLinkPasswordByDefault || this.isPasswordEnforced) && this.isPublicShare) { this.$set(this.share, 'newPassword', await GeneratePassword(true)) this.advancedSectionAccordionExpanded = true } @@ -906,8 +907,9 @@ export default { this.advancedSectionAccordionExpanded = true } - if (this.share.note) { + if (this.isValidShareAttribute(this.share.note)) { this.writeNoteToRecipientIsChecked = true + this.advancedSectionAccordionExpanded = true } }, @@ -973,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.isNewShare && !this.isValidShareAttribute(this.share.password)) { + if (this.isPasswordEnforced && this.isNewShare && !this.isValidShareAttribute(this.share.password)) { this.passwordError = true } } else { @@ -1000,11 +999,19 @@ 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 } - this.creating = true - const share = await this.addShare(incomingShare) // 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) @@ -1018,14 +1025,14 @@ export default { } } } + 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) - emit('update:share', this.share) - this.queueUpdate(...permissionsAndAttributes) } await this.getNode() @@ -1102,10 +1109,6 @@ export default { * "sendPasswordByTalk". */ onPasswordProtectedByTalkChange() { - if (this.hasUnsavedPassword) { - this.share.password = this.share.newPassword.trim() - } - this.queueUpdate('sendPasswordByTalk', 'password') }, isValidShareAttribute(value) { diff --git a/apps/files_sharing/src/views/SharingLinkList.vue b/apps/files_sharing/src/views/SharingLinkList.vue index 3dd6fdf317b..c3d9a7f83dc 100644 --- a/apps/files_sharing/src/views/SharingLinkList.vue +++ b/apps/files_sharing/src/views/SharingLinkList.vue @@ -7,12 +7,6 @@ <ul v-if="canLinkShare" :aria-label="t('files_sharing', 'Link shares')" class="sharing-link-list"> - <!-- If no link shares, show the add link default entry --> - <SharingEntryLink v-if="!hasLinkShares && canReshare" - :can-reshare="canReshare" - :file-info="fileInfo" - @add:share="addShare" /> - <!-- Else we display the list --> <template v-if="hasShares"> <!-- using shares[index] to work with .sync --> @@ -27,6 +21,12 @@ @remove:share="removeShare" @open-sharing-details="openSharingDetails(share)" /> </template> + + <!-- If no link shares, show the add link default entry --> + <SharingEntryLink v-if="!hasLinkShares && canReshare" + :can-reshare="canReshare" + :file-info="fileInfo" + @add:share="addShare" /> </ul> </template> diff --git a/apps/files_sharing/src/views/SharingTab.vue b/apps/files_sharing/src/views/SharingTab.vue index 9caa1a0973a..bba8f0b2edb 100644 --- a/apps/files_sharing/src/views/SharingTab.vue +++ b/apps/files_sharing/src/views/SharingTab.vue @@ -50,7 +50,7 @@ :link-shares="linkShares" :reshare="reshare" :shares="shares" - :placeholder="t('files_sharing', 'Share with accounts and teams')" + :placeholder="internalShareInputPlaceholder" @open-sharing-details="toggleShareDetailsView" /> <!-- other shares list --> @@ -90,12 +90,17 @@ :file-info="fileInfo" :link-shares="linkShares" :is-external="true" - :placeholder="t('files_sharing', 'Email, federated cloud id')" + :placeholder="externalShareInputPlaceholder" :reshare="reshare" :shares="shares" @open-sharing-details="toggleShareDetailsView" /> + <!-- Non link external shares list --> + <SharingList v-if="!loading" + :shares="externalShares" + :file-info="fileInfo" + @open-sharing-details="toggleShareDetailsView" /> <!-- link shares list --> - <SharingLinkList v-if="!loading" + <SharingLinkList v-if="!loading && isLinkSharingAllowed" ref="linkShareList" :can-reshare="canReshare" :file-info="fileInfo" @@ -133,7 +138,7 @@ <div v-if="projectsEnabled" v-show="!showSharingDetailsView && fileInfo" class="sharingTab__additionalContent"> - <CollectionList :id="`${fileInfo.id}`" + <NcCollectionList :id="`${fileInfo.id}`" type="file" :name="fileInfo.name" /> </div> @@ -152,19 +157,20 @@ <script> import { getCurrentUser } from '@nextcloud/auth' +import { getCapabilities } from '@nextcloud/capabilities' import { orderBy } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' import { generateOcsUrl } from '@nextcloud/router' -import { CollectionList } from 'nextcloud-vue-collections' import { ShareType } from '@nextcloud/sharing' -import InfoIcon from 'vue-material-design-icons/Information.vue' +import NcAvatar from '@nextcloud/vue/components/NcAvatar' +import NcButton from '@nextcloud/vue/components/NcButton' +import NcCollectionList from '@nextcloud/vue/components/NcCollectionList' import NcPopover from '@nextcloud/vue/components/NcPopover' +import InfoIcon from 'vue-material-design-icons/InformationOutline.vue' import axios from '@nextcloud/axios' import moment from '@nextcloud/moment' -import NcAvatar from '@nextcloud/vue/components/NcAvatar' -import NcButton from '@nextcloud/vue/components/NcButton' import { shareWithTitle } from '../utils/SharedWithMe.js' @@ -180,15 +186,16 @@ import SharingList from './SharingList.vue' import SharingDetailsTab from './SharingDetailsTab.vue' import ShareDetails from '../mixins/ShareDetails.js' +import logger from '../services/logger.ts' export default { name: 'SharingTab', components: { - CollectionList, InfoIcon, NcAvatar, NcButton, + NcCollectionList, NcPopover, SharingEntryInternal, SharingEntrySimple, @@ -215,6 +222,7 @@ export default { sharedWithMe: {}, shares: [], linkShares: [], + externalShares: [], sections: OCA.Sharing.ShareTabSections.getSections(), projectsEnabled: loadState('core', 'projects_enabled', false), @@ -222,9 +230,9 @@ export default { shareDetailsData: {}, returnFocusElement: null, - internalSharesHelpText: t('files_sharing', 'Use this method to share files with individuals or teams within your organization. If the recipient already has access to the share but cannot locate it, you can send them the internal share link for easy access.'), - externalSharesHelpText: t('files_sharing', 'Use this method to share files with individuals or organizations outside your organization. Files and folders can be shared via public share links and email addresses. You can also share to other Nextcloud accounts hosted on different instances using their federated cloud ID.'), - additionalSharesHelpText: t('files_sharing', 'Shares that are not part of the internal or external shares. This can be shares from apps or other sources.'), + internalSharesHelpText: t('files_sharing', 'Share files within your organization. Recipients who can already view the file can also use this link for easy access.'), + externalSharesHelpText: t('files_sharing', 'Share files with others outside your organization via public links and email addresses. You can also share to Nextcloud accounts on other instances using their federated cloud ID.'), + additionalSharesHelpText: t('files_sharing', 'Shares from apps or other sources which are not included in internal or external shares.'), } }, @@ -235,15 +243,50 @@ export default { * @return {boolean} */ isSharedWithMe() { - return Object.keys(this.sharedWithMe).length > 0 + return !!this.sharedWithMe?.user + }, + + /** + * Is link sharing allowed for the current user? + * + * @return {boolean} + */ + isLinkSharingAllowed() { + const currentUser = getCurrentUser() + if (!currentUser) { + return false + } + + const capabilities = getCapabilities() + const publicSharing = capabilities.files_sharing?.public || {} + return publicSharing.enabled === true }, canReshare() { return !!(this.fileInfo.permissions & OC.PERMISSION_SHARE) || !!(this.reshare && this.reshare.hasSharePermission && this.config.isResharingAllowed) }, - }, + internalShareInputPlaceholder() { + return this.config.showFederatedSharesAsInternal && this.config.isFederationEnabled + // TRANSLATORS: Type as in with a keyboard + ? t('files_sharing', 'Type names, teams, federated cloud IDs') + // TRANSLATORS: Type as in with a keyboard + : t('files_sharing', 'Type names or teams') + }, + + externalShareInputPlaceholder() { + if (!this.isLinkSharingAllowed) { + // TRANSLATORS: Type as in with a keyboard + return this.config.isFederationEnabled ? t('files_sharing', 'Type a federated cloud ID') : '' + } + return !this.config.showFederatedSharesAsInternal && !this.config.isFederationEnabled + // TRANSLATORS: Type as in with a keyboard + ? t('files_sharing', 'Type an email') + // TRANSLATORS: Type as in with a keyboard + : t('files_sharing', 'Type an email or federated cloud ID') + }, + }, methods: { /** * Update current fileInfo and fetch new data @@ -255,7 +298,6 @@ export default { this.resetState() this.getShares() }, - /** * Get the existing shares infos */ @@ -358,11 +400,29 @@ export default { ], ) - this.linkShares = shares.filter(share => share.type === ShareType.Link || share.type === ShareType.Email) - this.shares = shares.filter(share => share.type !== ShareType.Link && share.type !== ShareType.Email) + for (const share of shares) { + if ([ShareType.Link, ShareType.Email].includes(share.type)) { + this.linkShares.push(share) + } else if ([ShareType.Remote, ShareType.RemoteGroup].includes(share.type)) { + if (this.config.showFederatedSharesToTrustedServersAsInternal) { + if (share.isTrustedServer) { + this.shares.push(share) + } else { + this.externalShares.push(share) + } + } else if (this.config.showFederatedSharesAsInternal) { + this.shares.push(share) + } else { + this.externalShares.push(share) + } + } else { + this.shares.push(share) + } + } - console.debug('Processed', this.linkShares.length, 'link share(s)') - console.debug('Processed', this.shares.length, 'share(s)') + logger.debug(`Processed ${this.linkShares.length} link share(s)`) + logger.debug(`Processed ${this.shares.length} share(s)`) + logger.debug(`Processed ${this.externalShares.length} external share(s)`) } }, @@ -423,6 +483,16 @@ export default { // meaning: not from the ShareInput if (share.type === ShareType.Email) { this.linkShares.unshift(share) + } else if ([ShareType.Remote, ShareType.RemoteGroup].includes(share.type)) { + if (this.config.showFederatedSharesAsInternal) { + this.shares.unshift(share) + } if (this.config.showFederatedSharesToTrustedServersAsInternal) { + if (share.isTrustedServer) { + this.shares.unshift(share) + } + } else { + this.externalShares.unshift(share) + } } else { this.shares.unshift(share) } |