1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162 |
- <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__wrapper">
- <div ref="quickPermissions" class="sharingTabDetailsView__quick-permissions">
- <div>
- <NcCheckboxRadioSwitch :button-variant="true"
- data-cy-files-sharing-share-permissions-bundle="read-only"
- :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"
- data-cy-files-sharing-share-permissions-bundle="upload-edit"
- :checked.sync="sharingPermission"
- :value="bundledPermissions.ALL.toString()"
- name="sharing_permission_radio"
- type="radio"
- button-variant-grouped="vertical"
- @update:checked="toggleCustomPermissions">
- <template v-if="allowsFileDrop">
- {{ t('files_sharing', 'Allow upload and editing') }}
- </template>
- <template v-else>
- {{ t('files_sharing', 'Allow editing') }}
- </template>
- <template #icon>
- <EditIcon :size="20" />
- </template>
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch v-if="allowsFileDrop"
- data-cy-files-sharing-share-permissions-bundle="file-drop"
- :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 class="subline">{{ t('files_sharing', 'Upload only') }}</small>
- <template #icon>
- <UploadIcon :size="20" />
- </template>
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch :button-variant="true"
- data-cy-files-sharing-share-permissions-bundle="custom"
- :checked.sync="sharingPermission"
- :value="'custom'"
- name="sharing_permission_radio"
- type="radio"
- button-variant-grouped="vertical"
- @update:checked="expandCustomPermissions">
- {{ t('files_sharing', 'Custom permissions') }}
- <small class="subline">{{ customPermissionsList }}</small>
- <template #icon>
- <DotsHorizontalIcon :size="20" />
- </template>
- </NcCheckboxRadioSwitch>
- </div>
- </div>
- <div class="sharingTabDetailsView__advanced-control">
- <NcButton id="advancedSectionAccordionAdvancedControl"
- type="tertiary"
- alignment="end-reverse"
- aria-controls="advancedSectionAccordionAdvanced"
- :aria-expanded="advancedControlExpandedValue"
- @click="advancedSectionAccordionExpanded = !advancedSectionAccordionExpanded">
- {{ t('files_sharing', 'Advanced settings') }}
- <template #icon>
- <MenuDownIcon v-if="!advancedSectionAccordionExpanded" />
- <MenuUpIcon v-else />
- </template>
- </NcButton>
- </div>
- <div v-if="advancedSectionAccordionExpanded"
- id="advancedSectionAccordionAdvanced"
- class="sharingTabDetailsView__advanced"
- aria-labelledby="advancedSectionAccordionAdvancedControl"
- role="region">
- <section>
- <NcInputField v-if="isPublicShare"
- autocomplete="off"
- :label="t('files_sharing', 'Share label')"
- :value.sync="share.label" />
- <template v-if="isPublicShare">
- <NcCheckboxRadioSwitch :checked.sync="isPasswordProtected" :disabled="isPasswordEnforced">
- {{ t('files_sharing', 'Set password') }}
- </NcCheckboxRadioSwitch>
- <NcPasswordField v-if="isPasswordProtected"
- autocomplete="new-password"
- :value="hasUnsavedPassword ? share.newPassword : ''"
- :error="passwordError"
- :helper-text="errorPasswordLabel"
- :required="isPasswordEnforced"
- :label="t('files_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 v-if="canTogglePasswordProtectedByTalkAvailable"
- :checked.sync="isPasswordProtectedByTalk"
- @update:checked="onPasswordProtectedByTalkChange">
- {{ t('files_sharing', 'Video verification') }}
- </NcCheckboxRadioSwitch>
- <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 ?? dateTomorrow)"
- :min="dateTomorrow"
- :max="maxExpirationDateEnforced"
- :hide-label="true"
- :placeholder="t('files_sharing', 'Expiration date')"
- type="date"
- @input="onExpirationChange" />
- <NcCheckboxRadioSwitch v-if="isPublicShare"
- :disabled="canChangeHideDownload"
- :checked.sync="share.hideDownload"
- @update:checked="queueUpdate('hideDownload')">
- {{ t('files_sharing', 'Hide download') }}
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch v-if="!isPublicShare"
- :disabled="!canSetDownload"
- :checked.sync="canDownload"
- data-cy-files-sharing-share-permissions-checkbox="download">
- {{ t('files_sharing', 'Allow download') }}
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch :checked.sync="writeNoteToRecipientIsChecked">
- {{ t('files_sharing', 'Note to recipient') }}
- </NcCheckboxRadioSwitch>
- <template v-if="writeNoteToRecipientIsChecked">
- <label for="share-note-textarea">
- {{ t('files_sharing', 'Enter a note for the share recipient') }}
- </label>
- <textarea id="share-note-textarea" :value="share.note" @input="share.note = $event.target.value" />
- </template>
- <ExternalShareAction v-for="action in externalLinkActions"
- :id="action.id"
- ref="externalLinkActions"
- :key="action.id"
- :action="action"
- :file-info="fileInfo"
- :share="share" />
- <NcCheckboxRadioSwitch :checked.sync="setCustomPermissions">
- {{ t('files_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"
- data-cy-files-sharing-share-permissions-checkbox="read">
- {{ t('files_sharing', 'Read') }}
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch v-if="isFolder"
- :disabled="!canSetCreate"
- :checked.sync="canCreate"
- data-cy-files-sharing-share-permissions-checkbox="create">
- {{ t('files_sharing', 'Create') }}
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch :disabled="!canSetEdit"
- :checked.sync="canEdit"
- data-cy-files-sharing-share-permissions-checkbox="update">
- {{ t('files_sharing', 'Edit') }}
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch v-if="config.isResharingAllowed && share.type !== SHARE_TYPES.SHARE_TYPE_LINK"
- :disabled="!canSetReshare"
- :checked.sync="canReshare"
- data-cy-files-sharing-share-permissions-checkbox="share">
- {{ t('files_sharing', 'Share') }}
- </NcCheckboxRadioSwitch>
- <NcCheckboxRadioSwitch :disabled="!canSetDelete"
- :checked.sync="canDelete"
- data-cy-files-sharing-share-permissions-checkbox="delete">
- {{ 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>
-
- <div class="sharingTabDetailsView__footer">
- <div class="button-group">
- <NcButton data-cy-files-sharing-share-editor-action="cancel"
- @click="$emit('close-sharing-details')">
- {{ t('files_sharing', 'Cancel') }}
- </NcButton>
- <NcButton type="primary"
- data-cy-files-sharing-share-editor-action="save"
- @click="saveShare">
- {{ shareButtonText }}
- <template v-if="creating" #icon>
- <NcLoadingIcon />
- </template>
- </NcButton>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import { getLanguage } from '@nextcloud/l10n'
- import { Type as ShareType } from '@nextcloud/sharing'
-
- import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
- import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
- import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
- import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
- import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
- import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
- import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
- import 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 MenuUpIcon from 'vue-material-design-icons/MenuUp.vue'
- import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
-
- import ExternalShareAction from '../components/ExternalShareAction.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,
- NcPasswordField,
- NcDateTimePickerNative,
- NcCheckboxRadioSwitch,
- NcLoadingIcon,
- CloseIcon,
- CircleIcon,
- EditIcon,
- ExternalShareAction,
- LinkIcon,
- GroupIcon,
- ShareIcon,
- UserIcon,
- UploadIcon,
- ViewIcon,
- MenuDownIcon,
- MenuUpIcon,
- 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: BUNDLED_PERMISSIONS.ALL.toString(),
- setCustomPermissions: false,
- passwordError: false,
- advancedSectionAccordionExpanded: false,
- bundledPermissions: BUNDLED_PERMISSIONS,
- isFirstComponentLoad: true,
- test: false,
- creating: false,
-
- ExternalShareActions: OCA.Sharing.ExternalShareActions.state,
- }
- },
-
- computed: {
- title() {
- switch (this.share.type) {
- case this.SHARE_TYPES.SHARE_TYPE_USER:
- return t('files_sharing', 'Share with {userName}', { userName: this.share.shareWithDisplayName })
- case this.SHARE_TYPES.SHARE_TYPE_EMAIL:
- return t('files_sharing', 'Share with email {email}', { email: this.share.shareWith })
- case this.SHARE_TYPES.SHARE_TYPE_LINK:
- return t('files_sharing', 'Share link')
- case this.SHARE_TYPES.SHARE_TYPE_GROUP:
- return t('files_sharing', 'Share with group')
- case this.SHARE_TYPES.SHARE_TYPE_ROOM:
- return t('files_sharing', 'Share in conversation')
- case this.SHARE_TYPES.SHARE_TYPE_REMOTE: {
- const [user, server] = this.share.shareWith.split('@')
- return t('files_sharing', 'Share with {user} on remote server {server}', { user, server })
- }
- case this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP:
- return t('files_sharing', 'Share with remote group')
- case this.SHARE_TYPES.SHARE_TYPE_GUEST:
- return t('files_sharing', 'Share with guest')
- default: {
- if (this.share.id) {
- // Share already exists
- return t('files_sharing', 'Update share')
- } else {
- return t('files_sharing', 'Create share')
- }
- }
- }
- },
- /**
- * 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.attributes.find(attr => attr.key === 'download')?.enabled || false
- },
- set(checked) {
- // Find the 'download' attribute and update its value
- const downloadAttr = this.share.attributes.find(attr => attr.key === 'download')
- if (downloadAttr) {
- downloadAttr.enabled = checked
- }
- },
- },
- /**
- * 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.isValidShareAttribute(this.share.expireDate)
- },
- 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) {
- if (enabled) {
- this.share.password = await GeneratePassword()
- this.$set(this.share, 'newPassword', this.share.password)
- } else {
- this.share.password = ''
- this.$delete(this.share, 'newPassword')
- }
- },
- },
- /**
- * Is the current share a folder ?
- *
- * @return {boolean}
- */
- isFolder() {
- return this.fileInfo.type === 'dir'
- },
- /**
- * @return {boolean}
- */
- isSetDownloadButtonVisible() {
- 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
- },
- 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
- },
- isNewShare() {
- return !this.share.id
- },
- allowsFileDrop() {
- if (this.isFolder && this.config.isPublicUploadEnabled) {
- if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK || this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL) {
- return true
- }
- }
- return false
- },
- hasFileDropPermissions() {
- return this.share.permissions === this.bundledPermissions.FILE_DROP
- },
- shareButtonText() {
- if (this.isNewShare) {
- return t('files_sharing', 'Save share')
- }
- return t('files_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.isValidShareAttribute(this.share.passwordExpirationTime)) {
- 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
- }
-
- // Is Talk enabled?
- return OC.appswebroots.spreed !== undefined
- },
- canChangeHideDownload() {
- const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.enabled === false
- return this.fileInfo.shareAttributes.some(hasDisabledDownload)
- },
- customPermissionsList() {
- // Key order will be different, because ATOMIC_PERMISSIONS are numbers
- const translatedPermissions = {
- [ATOMIC_PERMISSIONS.READ]: this.t('files_sharing', 'Read'),
- [ATOMIC_PERMISSIONS.CREATE]: this.t('files_sharing', 'Create'),
- [ATOMIC_PERMISSIONS.UPDATE]: this.t('files_sharing', 'Edit'),
- [ATOMIC_PERMISSIONS.SHARE]: this.t('files_sharing', 'Share'),
- [ATOMIC_PERMISSIONS.DELETE]: this.t('files_sharing', 'Delete'),
- }
-
- return [ATOMIC_PERMISSIONS.READ, ATOMIC_PERMISSIONS.CREATE, ATOMIC_PERMISSIONS.UPDATE, ATOMIC_PERMISSIONS.SHARE, ATOMIC_PERMISSIONS.DELETE]
- .filter((permission) => hasPermissions(this.share.permissions, permission))
- .map((permission, index) => index === 0
- ? translatedPermissions[permission]
- : translatedPermissions[permission].toLocaleLowerCase(getLanguage()))
- .join(', ')
- },
- advancedControlExpandedValue() {
- return this.advancedSectionAccordionExpanded ? 'true' : 'false'
- },
- errorPasswordLabel() {
- if (this.passwordError) {
- return t('files_sharing', "Password field can't be empty")
- }
- return undefined
- },
-
- /**
- * Additional actions for the menu
- *
- * @return {Array}
- */
- externalLinkActions() {
- const filterValidAction = (action) => (action.shareType.includes(ShareType.SHARE_TYPE_LINK) || action.shareType.includes(ShareType.SHARE_TYPE_EMAIL)) && action.advanced
- // filter only the advanced registered actions for said link
- return this.ExternalShareActions.actions
- .filter(filterValidAction)
- },
- },
- 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)
- },
-
- mounted() {
- this.$refs.quickPermissions?.querySelector('input:checked')?.focus()
- },
-
- methods: {
- updateAtomicPermissions({
- isReadChecked = this.hasRead,
- isEditChecked = this.canEdit,
- isCreateChecked = this.canCreate,
- isDeleteChecked = this.canDelete,
- isReshareChecked = this.canReshare,
- } = {}) {
- // 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
- },
- expandCustomPermissions() {
- if (!this.advancedSectionAccordionExpanded) {
- this.advancedSectionAccordionExpanded = true
- }
- this.toggleCustomPermissions()
- },
- toggleCustomPermissions(selectedPermission) {
- const isCustomPermissions = this.sharingPermission === 'custom'
- this.revertSharingPermission = !isCustomPermissions ? selectedPermission : 'custom'
- this.setCustomPermissions = isCustomPermissions
- },
- async initializeAttributes() {
-
- if (this.isNewShare) {
- if (this.isPasswordEnforced && this.isPublicShare) {
- this.$set(this.share, 'newPassword', await GeneratePassword())
- this.advancedSectionAccordionExpanded = true
- }
- /* Set default expiration dates if configured */
- if (this.isPublicShare && this.config.isDefaultExpireDateEnabled) {
- this.share.expireDate = this.config.defaultExpirationDate.toDateString()
- } else if (this.isRemoteShare && this.config.isDefaultRemoteExpireDateEnabled) {
- this.share.expireDate = this.config.defaultRemoteExpirationDateString.toDateString()
- } else if (this.config.isDefaultInternalExpireDateEnabled) {
- this.share.expireDate = this.config.defaultInternalExpirationDate.toDateString()
- }
-
- if (this.isValidShareAttribute(this.share.expireDate)) {
- this.advancedSectionAccordionExpanded = true
- }
-
- return
- }
-
- // If there is an enforced expiry date, then existing shares created before enforcement
- // have no expiry date, hence we set it here.
- if (!this.isValidShareAttribute(this.share.expireDate) && this.isExpiryDateEnforced) {
- this.hasExpirationDate = true
- }
-
- if (
- this.isValidShareAttribute(this.share.password)
- || this.isValidShareAttribute(this.share.expireDate)
- || this.isValidShareAttribute(this.share.label)
- ) {
- this.advancedSectionAccordionExpanded = true
- }
-
- },
- handleShareType() {
- if ('shareType' in this.share) {
- this.share.type = this.share.shareType
- } else if (this.share.share_type) {
- this.share.type = this.share.share_type
- }
- },
- handleDefaultPermissions() {
- if (this.isNewShare) {
- const defaultPermissions = this.config.defaultPermissions
- if (defaultPermissions === BUNDLED_PERMISSIONS.READ_ONLY || defaultPermissions === BUNDLED_PERMISSIONS.ALL) {
- this.sharingPermission = defaultPermissions.toString()
- } else {
- this.sharingPermission = 'custom'
- this.share.permissions = defaultPermissions
- this.advancedSectionAccordionExpanded = true
- this.setCustomPermissions = true
- }
- }
- },
- handleCustomPermissions() {
- if (!this.isNewShare && (this.hasCustomPermissions || this.share.setCustomPermissions)) {
- this.sharingPermission = 'custom'
- this.advancedSectionAccordionExpanded = true
- this.setCustomPermissions = true
- } else if (this.share.permissions) {
- this.sharingPermission = this.share.permissions.toString()
- }
- },
- initializePermissions() {
- this.handleShareType()
- this.handleDefaultPermissions()
- this.handleCustomPermissions()
- },
- 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.hasUnsavedPassword && this.isValidShareAttribute(this.share.newPassword)) {
- this.share.password = this.share.newPassword
- this.$delete(this.share, 'newPassword')
- } else if (this.isPasswordEnforced && !this.isValidShareAttribute(this.share.password)) {
- this.passwordError = true
- }
- } 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,
- fileInfo: this.fileInfo,
- }
-
- incomingShare.expireDate = this.hasExpirationDate ? this.share.expireDate : ''
-
- if (this.isPasswordProtected) {
- incomingShare.password = this.share.password
- }
-
- this.creating = true
- const share = await this.addShare(incomingShare, this.fileInfo)
- this.creating = false
- this.share = share
- this.$emit('add:share', this.share)
- } else {
- this.queueUpdate(...permissionsAndAttributes)
- }
-
- if (this.$refs.externalLinkActions?.length > 0) {
- await Promise.allSettled(this.$refs.externalLinkActions.map((action) => {
- if (typeof action.$children.at(0)?.onSave !== 'function') {
- return Promise.resolve()
- }
- return action.$children.at(0)?.onSave?.()
- }))
- }
-
- this.$emit('close-sharing-details')
- },
- /**
- * Process the new share request
- *
- * @param {Share} share incoming share object
- * @param {object} fileInfo file data
- */
- async addShare(share, fileInfo) {
- console.debug('Adding a new share from the input for', share)
- try {
- const path = (fileInfo.path + '/' + fileInfo.name).replace('//', '/')
- const resultingShare = await this.createShare({
- path,
- shareType: share.shareType,
- shareWith: share.shareWith,
- permissions: share.permissions,
- expireDate: share.expireDate,
- attributes: JSON.stringify(share.attributes),
- ...(share.note ? { note: share.note } : {}),
- ...(share.password ? { password: share.password } : {}),
- })
- return resultingShare
- } 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;
- width: 100%;
- margin: 0 auto;
- position: relative;
- height: 100%;
- overflow: hidden;
-
- &__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;
- }
-
- }
- }
-
- &__wrapper {
- position: relative;
- overflow: scroll;
- flex-shrink: 1;
- padding: 4px;
- padding-right: 12px;
- }
-
- &__quick-permissions {
- display: flex;
- justify-content: center;
- width: 100%;
- margin: 0 auto;
- border-radius: 0;
-
- div {
- width: 100%;
-
- span {
- width: 100%;
-
- span:nth-child(1) {
- align-items: center;
- justify-content: center;
- padding: 0.1em;
- }
-
- ::v-deep label {
-
- span {
- display: flex;
- flex-direction: column;
- }
- }
-
- /* Target component based style in NcCheckboxRadioSwitch slot content*/
- :deep(span.checkbox-content__text.checkbox-radio-switch__text) {
- flex-wrap: wrap;
-
- .subline {
- display: block;
- flex-basis: 100%;
- }
- }
- }
-
- }
- }
-
- &__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;
- margin: 0;
- }
-
- /*
- 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;
- }
- }
- }
-
- &__delete {
- >button:first-child {
- color: rgb(223, 7, 7);
- }
- }
-
- &__footer {
- width: 100%;
- display: flex;
- position: sticky;
- bottom: 0;
- flex-direction: column;
- justify-content: space-between;
- align-items: flex-start;
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--color-main-background));
-
- .button-group {
- display: flex;
- justify-content: space-between;
- width: 100%;
- margin-top: 16px;
-
- button {
- margin-left: 16px;
-
- &:first-child {
- margin-left: 0;
- }
- }
- }
- }
- }
- </style>
|