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