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.vue224
1 files changed, 140 insertions, 84 deletions
diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue
index f50a533eeeb..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"
@@ -115,8 +115,8 @@
: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')"
- @trailing-button-click="generateNewToken"
- :value.sync="share.token">
+ :value.sync="share.token"
+ @trailing-button-click="generateNewToken">
<template #trailing-button-icon>
<NcLoadingIcon v-if="loadingToken" />
<Refresh v-else :size="20" />
@@ -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"
@@ -169,7 +169,7 @@
@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">
@@ -183,6 +183,10 @@
: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"
@@ -222,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>
@@ -245,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>
@@ -265,18 +270,18 @@ import { ShareType } from '@nextcloud/sharing'
import { showError } from '@nextcloud/dialogs'
import moment from '@nextcloud/moment'
-import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
-import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
-import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
-import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
-import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
-import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
-import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
-import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'
+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'
@@ -368,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:
@@ -379,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:
@@ -395,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 ?
*/
@@ -439,28 +450,29 @@ 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')?.value ?? true
+ 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.value = checked
- } else {
- if (this.share.attributes === null) {
- this.$set(this.share, 'attributes', [])
- }
- this.share.attributes.push({
- scope: 'permissions',
- key: 'download',
- value: checked,
- })
- }
+ this.setShareAttribute('permissions', 'download', checked)
},
},
/**
@@ -491,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}
@@ -556,9 +548,6 @@ export default {
isGroupShare() {
return this.share.type === ShareType.Group
},
- isNewShare() {
- return !this.share.id
- },
allowsFileDrop() {
if (this.isFolder && this.config.isPublicUploadEnabled) {
if (this.share.type === ShareType.Link || this.share.type === ShareType.Email) {
@@ -729,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()))
@@ -741,7 +737,7 @@ 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
},
@@ -786,6 +782,42 @@ 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
@@ -812,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)
@@ -834,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
}
@@ -868,8 +907,9 @@ export default {
this.advancedSectionAccordionExpanded = true
}
- if (this.share.note) {
+ if (this.isValidShareAttribute(this.share.note)) {
this.writeNoteToRecipientIsChecked = true
+ this.advancedSectionAccordionExpanded = true
}
},
@@ -935,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 {
@@ -962,17 +999,40 @@ 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.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()
@@ -1049,10 +1109,6 @@ export default {
* "sendPasswordByTalk".
*/
onPasswordProtectedByTalkChange() {
- if (this.hasUnsavedPassword) {
- this.share.password = this.share.newPassword.trim()
- }
-
this.queueUpdate('sendPasswordByTalk', 'password')
},
isValidShareAttribute(value) {