mirror of
https://github.com/nextcloud/server.git
synced 2026-03-05 23:11:08 -05:00
Improve sharing flow
This commit introduces the following changes: - Does not create new share once user is selected for internal shares - Adds a `SharingDetails` view for share configurations - Adds a quick share select to enable fast changes in share permisions. Resolves: https://github.com/nextcloud/server/issues/26691 Signed-off-by: fenn-cs <fenn25.fn@gmail.com> Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
parent
191e20d7f4
commit
8b42fb033f
20 changed files with 1474 additions and 562 deletions
|
|
@ -29,147 +29,64 @@
|
|||
:menu-position="'left'"
|
||||
:url="share.shareWithAvatar" />
|
||||
|
||||
<component :is="share.shareWithLink ? 'a' : 'div'"
|
||||
:title="tooltip"
|
||||
:aria-label="tooltip"
|
||||
:href="share.shareWithLink"
|
||||
class="sharing-entry__desc">
|
||||
<span>{{ title }}<span v-if="!isUnique" class="sharing-entry__desc-unique"> ({{ share.shareWithDisplayNameUnique }})</span></span>
|
||||
<p v-if="hasStatus">
|
||||
<span>{{ share.status.icon || '' }}</span>
|
||||
<span>{{ share.status.message || '' }}</span>
|
||||
</p>
|
||||
</component>
|
||||
<NcActions menu-align="right"
|
||||
class="sharing-entry__actions"
|
||||
@close="onMenuClose">
|
||||
<template v-if="share.canEdit">
|
||||
<!-- edit permission -->
|
||||
<NcActionCheckbox ref="canEdit"
|
||||
:checked.sync="canEdit"
|
||||
:value="permissionsEdit"
|
||||
:disabled="saving || !canSetEdit">
|
||||
{{ t('files_sharing', 'Allow editing') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<!-- create permission -->
|
||||
<NcActionCheckbox v-if="isFolder"
|
||||
ref="canCreate"
|
||||
:checked.sync="canCreate"
|
||||
:value="permissionsCreate"
|
||||
:disabled="saving || !canSetCreate">
|
||||
{{ t('files_sharing', 'Allow creating') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<!-- delete permission -->
|
||||
<NcActionCheckbox v-if="isFolder"
|
||||
ref="canDelete"
|
||||
:checked.sync="canDelete"
|
||||
:value="permissionsDelete"
|
||||
:disabled="saving || !canSetDelete">
|
||||
{{ t('files_sharing', 'Allow deleting') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<!-- reshare permission -->
|
||||
<NcActionCheckbox v-if="config.isResharingAllowed"
|
||||
ref="canReshare"
|
||||
:checked.sync="canReshare"
|
||||
:value="permissionsShare"
|
||||
:disabled="saving || !canSetReshare">
|
||||
{{ t('files_sharing', 'Allow resharing') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<NcActionCheckbox v-if="isSetDownloadButtonVisible"
|
||||
ref="canDownload"
|
||||
:checked.sync="canDownload"
|
||||
:disabled="saving || !canSetDownload">
|
||||
{{ allowDownloadText }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<!-- expiration date -->
|
||||
<NcActionCheckbox :checked.sync="hasExpirationDate"
|
||||
:disabled="config.isDefaultInternalExpireDateEnforced || saving"
|
||||
@uncheck="onExpirationDisable">
|
||||
{{ config.isDefaultInternalExpireDateEnforced
|
||||
? t('files_sharing', 'Expiration date enforced')
|
||||
: t('files_sharing', 'Set expiration date') }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionInput v-if="hasExpirationDate"
|
||||
ref="expireDate"
|
||||
:is-native-picker="true"
|
||||
:hide-label="true"
|
||||
:class="{ error: errors.expireDate}"
|
||||
:disabled="saving"
|
||||
:value="new Date(share.expireDate)"
|
||||
type="date"
|
||||
:min="dateTomorrow"
|
||||
:max="dateMaxEnforced"
|
||||
@input="onExpirationChange">
|
||||
{{ t('files_sharing', 'Enter a date') }}
|
||||
</NcActionInput>
|
||||
|
||||
<!-- note -->
|
||||
<template v-if="canHaveNote">
|
||||
<NcActionCheckbox :checked.sync="hasNote"
|
||||
:disabled="saving"
|
||||
@uncheck="queueUpdate('note')">
|
||||
{{ t('files_sharing', 'Note to recipient') }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionTextEditable v-if="hasNote"
|
||||
ref="note"
|
||||
:class="{ error: errors.note}"
|
||||
:disabled="saving"
|
||||
:value="share.newNote || share.note"
|
||||
icon="icon-edit"
|
||||
@update:value="onNoteChange"
|
||||
@submit="onNoteSubmit" />
|
||||
</template>
|
||||
<div class="sharing-entry__summary" @click.prevent="toggleQuickShareSelect">
|
||||
<component :is="share.shareWithLink ? 'a' : 'div'"
|
||||
:title="tooltip"
|
||||
:aria-label="tooltip"
|
||||
:href="share.shareWithLink"
|
||||
class="sharing-entry__desc">
|
||||
<span>{{ title }}<span v-if="!isUnique" class="sharing-entry__desc-unique"> ({{
|
||||
share.shareWithDisplayNameUnique }})</span></span>
|
||||
<p v-if="hasStatus">
|
||||
<span>{{ share.status.icon || '' }}</span>
|
||||
<span>{{ share.status.message || '' }}</span>
|
||||
</p>
|
||||
</component>
|
||||
<QuickShareSelect :share="share"
|
||||
:file-info="fileInfo"
|
||||
:toggle="showDropdown"
|
||||
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
|
||||
</div>
|
||||
<NcButton class="sharing-entry__action"
|
||||
:aria-label="t('files_sharing', 'Open Sharing Details')"
|
||||
type="tertiary-no-background"
|
||||
@click="openSharingDetails(share)">
|
||||
<template #icon>
|
||||
<DotsHorizontalIcon :size="20" />
|
||||
</template>
|
||||
|
||||
<NcActionButton v-if="share.canDelete"
|
||||
icon="icon-close"
|
||||
:disabled="saving"
|
||||
@click.prevent="onDelete">
|
||||
{{ t('files_sharing', 'Unshare') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</NcButton>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
|
||||
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
|
||||
import NcActionTextEditable from '@nextcloud/vue/dist/Components/NcActionTextEditable.js'
|
||||
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
|
||||
|
||||
import QuickShareSelect from './SharingEntryQuickShareSelect.vue'
|
||||
|
||||
import SharesMixin from '../mixins/SharesMixin.js'
|
||||
import ShareDetails from '../mixins/ShareDetails.js'
|
||||
|
||||
export default {
|
||||
name: 'SharingEntry',
|
||||
|
||||
components: {
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcActionCheckbox,
|
||||
NcActionInput,
|
||||
NcActionTextEditable,
|
||||
NcButton,
|
||||
NcAvatar,
|
||||
DotsHorizontalIcon,
|
||||
NcSelect,
|
||||
QuickShareSelect,
|
||||
},
|
||||
|
||||
mixins: [SharesMixin],
|
||||
mixins: [SharesMixin, ShareDetails],
|
||||
|
||||
data() {
|
||||
return {
|
||||
permissionsEdit: OC.PERMISSION_UPDATE,
|
||||
permissionsCreate: OC.PERMISSION_CREATE,
|
||||
permissionsDelete: OC.PERMISSION_DELETE,
|
||||
permissionsRead: OC.PERMISSION_READ,
|
||||
permissionsShare: OC.PERMISSION_SHARE,
|
||||
showDropdown: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
let title = this.share.shareWithDisplayName
|
||||
|
|
@ -186,7 +103,6 @@ export default {
|
|||
}
|
||||
return title
|
||||
},
|
||||
|
||||
tooltip() {
|
||||
if (this.share.owner !== this.share.uidFileOwner) {
|
||||
const data = {
|
||||
|
|
@ -206,182 +122,6 @@ export default {
|
|||
return null
|
||||
},
|
||||
|
||||
canHaveNote() {
|
||||
return !this.isRemote
|
||||
},
|
||||
|
||||
isRemote() {
|
||||
return this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE
|
||||
|| this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharer set whether the sharee can edit the file ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
canSetEdit() {
|
||||
// If the owner revoked the permission after the resharer granted it
|
||||
// the share still has the permission, and the resharer is still
|
||||
// allowed to revoke it too (but not to grant it again).
|
||||
return (this.fileInfo.sharePermissions & OC.PERMISSION_UPDATE) || this.canEdit
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharer set whether the sharee can create the file ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
canSetCreate() {
|
||||
// If the owner revoked the permission after the resharer granted it
|
||||
// the share still has the permission, and the resharer is still
|
||||
// allowed to revoke it too (but not to grant it again).
|
||||
return (this.fileInfo.sharePermissions & OC.PERMISSION_CREATE) || this.canCreate
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharer set whether the sharee can delete the file ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
canSetDelete() {
|
||||
// If the owner revoked the permission after the resharer granted it
|
||||
// the share still has the permission, and the resharer is still
|
||||
// allowed to revoke it too (but not to grant it again).
|
||||
return (this.fileInfo.sharePermissions & OC.PERMISSION_DELETE) || this.canDelete
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharer set whether the sharee can reshare the file ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
canSetReshare() {
|
||||
// If the owner revoked the permission after the resharer granted it
|
||||
// the share still has the permission, and the resharer is still
|
||||
// allowed to revoke it too (but not to grant it again).
|
||||
return (this.fileInfo.sharePermissions & OC.PERMISSION_SHARE) || this.canReshare
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharer set whether the sharee can download the file ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
canSetDownload() {
|
||||
// If the owner revoked the permission after the resharer granted it
|
||||
// the share still has the permission, and the resharer is still
|
||||
// allowed to revoke it too (but not to grant it again).
|
||||
return (this.fileInfo.canDownload() || this.canDownload)
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee edit the shared file ?
|
||||
*/
|
||||
canEdit: {
|
||||
get() {
|
||||
return this.share.hasUpdatePermission
|
||||
},
|
||||
set(checked) {
|
||||
this.updatePermissions({ isEditChecked: checked })
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee create the shared file ?
|
||||
*/
|
||||
canCreate: {
|
||||
get() {
|
||||
return this.share.hasCreatePermission
|
||||
},
|
||||
set(checked) {
|
||||
this.updatePermissions({ isCreateChecked: checked })
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee delete the shared file ?
|
||||
*/
|
||||
canDelete: {
|
||||
get() {
|
||||
return this.share.hasDeletePermission
|
||||
},
|
||||
set(checked) {
|
||||
this.updatePermissions({ isDeleteChecked: checked })
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee reshare the file ?
|
||||
*/
|
||||
canReshare: {
|
||||
get() {
|
||||
return this.share.hasSharePermission
|
||||
},
|
||||
set(checked) {
|
||||
this.updatePermissions({ isReshareChecked: checked })
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Can the sharee download files or only view them ?
|
||||
*/
|
||||
canDownload: {
|
||||
get() {
|
||||
return this.share.hasDownloadPermission
|
||||
},
|
||||
set(checked) {
|
||||
this.updatePermissions({ isDownloadChecked: checked })
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Is this share readable
|
||||
* Needed for some federated shares that might have been added from file drop links
|
||||
*/
|
||||
hasRead: {
|
||||
get() {
|
||||
return this.share.hasReadPermission
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the current share a folder ?
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isFolder() {
|
||||
return this.fileInfo.type === 'dir'
|
||||
},
|
||||
|
||||
/**
|
||||
* Does the current share have an expiration date
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
hasExpirationDate: {
|
||||
get() {
|
||||
return this.config.isDefaultInternalExpireDateEnforced || !!this.share.expireDate
|
||||
},
|
||||
set(enabled) {
|
||||
const defaultExpirationDate = this.config.defaultInternalExpirationDate
|
||||
|| new Date(new Date().setDate(new Date().getDate() + 1))
|
||||
this.share.expireDate = enabled
|
||||
? this.formatDateToString(defaultExpirationDate)
|
||||
: ''
|
||||
console.debug('Expiration date status', enabled, this.share.expireDate)
|
||||
},
|
||||
},
|
||||
|
||||
dateMaxEnforced() {
|
||||
if (!this.isRemote && this.config.isDefaultInternalExpireDateEnforced) {
|
||||
return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultInternalExpireDate))
|
||||
} else if (this.config.isDefaultRemoteExpireDateEnforced) {
|
||||
return new Date(new Date().setDate(new Date().getDate() + 1 + this.config.defaultRemoteExpireDate))
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
|
@ -392,70 +132,18 @@ export default {
|
|||
|
||||
return (typeof this.share.status === 'object' && !Array.isArray(this.share.status))
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
allowDownloadText() {
|
||||
return t('files_sharing', 'Allow download')
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isSetDownloadButtonVisible() {
|
||||
// TODO: Implement download permission for circle shares instead of hiding the option.
|
||||
// https://github.com/nextcloud/server/issues/39161
|
||||
if (this.share && this.share.type === this.SHARE_TYPES.SHARE_TYPE_CIRCLE) {
|
||||
return false
|
||||
}
|
||||
|
||||
const allowedMimetypes = [
|
||||
// Office documents
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'application/vnd.oasis.opendocument.presentation',
|
||||
]
|
||||
|
||||
return this.isFolder || allowedMimetypes.includes(this.fileInfo.mimetype)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
updatePermissions({
|
||||
isEditChecked = this.canEdit,
|
||||
isCreateChecked = this.canCreate,
|
||||
isDeleteChecked = this.canDelete,
|
||||
isReshareChecked = this.canReshare,
|
||||
isDownloadChecked = this.canDownload,
|
||||
} = {}) {
|
||||
// calc permissions if checked
|
||||
const permissions = 0
|
||||
| (this.hasRead ? this.permissionsRead : 0)
|
||||
| (isCreateChecked ? this.permissionsCreate : 0)
|
||||
| (isDeleteChecked ? this.permissionsDelete : 0)
|
||||
| (isEditChecked ? this.permissionsEdit : 0)
|
||||
| (isReshareChecked ? this.permissionsShare : 0)
|
||||
|
||||
this.share.permissions = permissions
|
||||
if (this.share.hasDownloadPermission !== isDownloadChecked) {
|
||||
this.share.hasDownloadPermission = isDownloadChecked
|
||||
}
|
||||
this.queueUpdate('permissions', 'attributes')
|
||||
},
|
||||
|
||||
/**
|
||||
* Save potential changed data on menu close
|
||||
*/
|
||||
onMenuClose() {
|
||||
this.onNoteSubmit()
|
||||
},
|
||||
toggleQuickShareSelect() {
|
||||
this.showDropdown = !this.showDropdown
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -465,21 +153,34 @@ export default {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
|
||||
&__desc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
padding-bottom: 0;
|
||||
line-height: 1.2em;
|
||||
|
||||
p {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
|
||||
&-unique {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__summary {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -25,13 +25,18 @@
|
|||
<NcAvatar :is-no-user="true"
|
||||
:icon-class="isEmailShareType ? 'avatar-link-share icon-mail-white' : 'avatar-link-share icon-public-white'"
|
||||
class="sharing-entry__avatar" />
|
||||
<div class="sharing-entry__desc">
|
||||
<div class="sharing-entry__desc" @click.prevent="toggleQuickShareSelect">
|
||||
<span class="sharing-entry__title" :title="title">
|
||||
{{ title }}
|
||||
</span>
|
||||
<p v-if="subtitle">
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
<QuickShareSelect v-if="share && share.permissions !== undefined"
|
||||
:share="share"
|
||||
:file-info="fileInfo"
|
||||
:toggle="showDropdown"
|
||||
@open-sharing-details="openShareDetailsForCustomSettings(share)" />
|
||||
</div>
|
||||
|
||||
<!-- clipboard -->
|
||||
|
|
@ -123,110 +128,13 @@
|
|||
@close="onMenuClose">
|
||||
<template v-if="share">
|
||||
<template v-if="share.canEdit && canReshare">
|
||||
<!-- Custom Label -->
|
||||
<NcActionInput ref="label"
|
||||
:class="{ error: errors.label }"
|
||||
:disabled="saving"
|
||||
:label="t('files_sharing', 'Share label')"
|
||||
:value="share.newLabel !== undefined ? share.newLabel : share.label"
|
||||
icon="icon-edit"
|
||||
maxlength="255"
|
||||
@update:value="onLabelChange"
|
||||
@submit="onLabelSubmit" />
|
||||
|
||||
<SharePermissionsEditor :can-reshare="canReshare"
|
||||
:share.sync="share"
|
||||
:file-info="fileInfo" />
|
||||
|
||||
<NcActionSeparator />
|
||||
|
||||
<NcActionCheckbox :checked.sync="share.hideDownload"
|
||||
:disabled="saving || canChangeHideDownload"
|
||||
@change="queueUpdate('hideDownload')">
|
||||
{{ t('files_sharing', 'Hide download') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<!-- password -->
|
||||
<NcActionCheckbox :checked.sync="isPasswordProtected"
|
||||
:disabled="config.enforcePasswordForPublicLink || saving"
|
||||
class="share-link-password-checkbox"
|
||||
@uncheck="onPasswordDisable">
|
||||
{{ config.enforcePasswordForPublicLink
|
||||
? t('files_sharing', 'Password protection (enforced)')
|
||||
: t('files_sharing', 'Password protect') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<NcActionInput v-if="isPasswordProtected"
|
||||
ref="password"
|
||||
class="share-link-password"
|
||||
:class="{ error: errors.password}"
|
||||
:disabled="saving"
|
||||
:show-trailing-button="hasUnsavedPassword"
|
||||
:required="config.enforcePasswordForPublicLink"
|
||||
:value="hasUnsavedPassword ? share.newPassword : '***************'"
|
||||
icon="icon-password"
|
||||
autocomplete="new-password"
|
||||
:type="hasUnsavedPassword ? 'text': 'password'"
|
||||
@update:value="onPasswordChange"
|
||||
@submit="onPasswordSubmit">
|
||||
{{ t('files_sharing', 'Enter a password') }}
|
||||
</NcActionInput>
|
||||
<NcActionText v-if="isEmailShareType && passwordExpirationTime" icon="icon-info">
|
||||
{{ t('files_sharing', 'Password expires {passwordExpirationTime}', {passwordExpirationTime}) }}
|
||||
</NcActionText>
|
||||
<NcActionText v-else-if="isEmailShareType && passwordExpirationTime !== null" icon="icon-error">
|
||||
{{ t('files_sharing', 'Password expired') }}
|
||||
</NcActionText>
|
||||
|
||||
<!-- password protected by Talk -->
|
||||
<NcActionCheckbox v-if="isPasswordProtectedByTalkAvailable"
|
||||
:checked.sync="isPasswordProtectedByTalk"
|
||||
:disabled="!canTogglePasswordProtectedByTalkAvailable || saving"
|
||||
class="share-link-password-talk-checkbox"
|
||||
@change="onPasswordProtectedByTalkChange">
|
||||
{{ t('files_sharing', 'Video verification') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<!-- expiration date -->
|
||||
<NcActionCheckbox :checked.sync="hasExpirationDate"
|
||||
:disabled="config.isDefaultExpireDateEnforced || saving"
|
||||
class="share-link-expire-date-checkbox"
|
||||
@uncheck="onExpirationDisable">
|
||||
{{ config.isDefaultExpireDateEnforced
|
||||
? t('files_sharing', 'Expiration date (enforced)')
|
||||
: t('files_sharing', 'Set expiration date') }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionInput v-if="hasExpirationDate"
|
||||
ref="expireDate"
|
||||
:is-native-picker="true"
|
||||
:hide-label="true"
|
||||
class="share-link-expire-date"
|
||||
:class="{ error: errors.expireDate}"
|
||||
:disabled="saving"
|
||||
:value="new Date(share.expireDate)"
|
||||
type="date"
|
||||
:min="dateTomorrow"
|
||||
:max="dateMaxEnforced"
|
||||
@input="onExpirationChange">
|
||||
{{ t('files_sharing', 'Enter a date') }}
|
||||
</NcActionInput>
|
||||
|
||||
<!-- note -->
|
||||
<NcActionCheckbox :checked.sync="hasNote"
|
||||
:disabled="saving"
|
||||
@uncheck="queueUpdate('note')">
|
||||
{{ t('files_sharing', 'Note to recipient') }}
|
||||
</NcActionCheckbox>
|
||||
|
||||
<NcActionTextEditable v-if="hasNote"
|
||||
ref="note"
|
||||
:class="{ error: errors.note}"
|
||||
:disabled="saving"
|
||||
:placeholder="t('files_sharing', 'Enter a note for the share recipient')"
|
||||
:value="share.newNote || share.note"
|
||||
icon="icon-edit"
|
||||
@update:value="onNoteChange"
|
||||
@submit="onNoteSubmit" />
|
||||
<NcActionButton :disabled="saving"
|
||||
@click.prevent="openSharingDetails">
|
||||
<template #icon>
|
||||
<Tune />
|
||||
</template>
|
||||
{{ t('files_sharing', 'Customize link') }}
|
||||
</NcActionButton>
|
||||
</template>
|
||||
|
||||
<NcActionSeparator />
|
||||
|
|
@ -248,18 +156,19 @@
|
|||
{{ name }}
|
||||
</NcActionLink>
|
||||
|
||||
<NcActionButton v-if="share.canDelete"
|
||||
icon="icon-close"
|
||||
:disabled="saving"
|
||||
@click.prevent="onDelete">
|
||||
{{ t('files_sharing', 'Unshare') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton v-if="!isEmailShareType && canReshare"
|
||||
class="new-share-link"
|
||||
icon="icon-add"
|
||||
@click.prevent.stop="onNewLinkShare">
|
||||
{{ t('files_sharing', 'Add another link') }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionButton v-if="share.canDelete"
|
||||
icon="icon-close"
|
||||
:disabled="saving"
|
||||
@click.prevent="onDelete">
|
||||
{{ t('files_sharing', 'Unshare') }}
|
||||
</NcActionButton>
|
||||
</template>
|
||||
|
||||
<!-- Create new share -->
|
||||
|
|
@ -283,39 +192,40 @@ import { Type as ShareTypes } from '@nextcloud/sharing'
|
|||
import Vue from 'vue'
|
||||
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
|
||||
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
|
||||
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
|
||||
import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js'
|
||||
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
|
||||
import NcActionTextEditable from '@nextcloud/vue/dist/Components/NcActionTextEditable.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
|
||||
import Tune from 'vue-material-design-icons/Tune.vue'
|
||||
|
||||
import QuickShareSelect from './SharingEntryQuickShareSelect.vue'
|
||||
|
||||
import ExternalShareAction from './ExternalShareAction.vue'
|
||||
import SharePermissionsEditor from './SharePermissionsEditor.vue'
|
||||
import GeneratePassword from '../utils/GeneratePassword.js'
|
||||
import Share from '../models/Share.js'
|
||||
import SharesMixin from '../mixins/SharesMixin.js'
|
||||
import ShareDetails from '../mixins/ShareDetails.js'
|
||||
|
||||
export default {
|
||||
name: 'SharingEntryLink',
|
||||
|
||||
components: {
|
||||
ExternalShareAction,
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcActionCheckbox,
|
||||
NcActionInput,
|
||||
NcActionLink,
|
||||
NcActionText,
|
||||
NcActionTextEditable,
|
||||
NcActionSeparator,
|
||||
NcAvatar,
|
||||
ExternalShareAction,
|
||||
SharePermissionsEditor,
|
||||
Tune,
|
||||
QuickShareSelect,
|
||||
},
|
||||
|
||||
mixins: [SharesMixin],
|
||||
mixins: [SharesMixin, ShareDetails],
|
||||
|
||||
props: {
|
||||
canReshare: {
|
||||
|
|
@ -330,6 +240,7 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
showDropdown: false,
|
||||
copySuccess: true,
|
||||
copied: false,
|
||||
|
||||
|
|
@ -593,7 +504,6 @@ export default {
|
|||
|
||||
canChangeHideDownload() {
|
||||
const hasDisabledDownload = (shareAttribute) => shareAttribute.key === 'download' && shareAttribute.scope === 'permissions' && shareAttribute.enabled === false
|
||||
|
||||
return this.fileInfo.shareAttributes.some(hasDisabledDownload)
|
||||
},
|
||||
},
|
||||
|
|
@ -671,7 +581,7 @@ export default {
|
|||
* accordingly
|
||||
*
|
||||
* @param {Share} share the new share
|
||||
* @param {boolean} [update=false] do we update the current share ?
|
||||
* @param {boolean} [update] do we update the current share ?
|
||||
*/
|
||||
async pushNewLinkShare(share, update) {
|
||||
try {
|
||||
|
|
@ -748,26 +658,6 @@ export default {
|
|||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Label changed, let's save it to a different key
|
||||
*
|
||||
* @param {string} label the share label
|
||||
*/
|
||||
onLabelChange(label) {
|
||||
this.$set(this.share, 'newLabel', label.trim())
|
||||
},
|
||||
|
||||
/**
|
||||
* When the note change, we trim, save and dispatch
|
||||
*/
|
||||
onLabelSubmit() {
|
||||
if (typeof this.share.newLabel === 'string') {
|
||||
this.share.label = this.share.newLabel
|
||||
this.$delete(this.share, 'newLabel')
|
||||
this.queueUpdate('label')
|
||||
}
|
||||
},
|
||||
async copyLink() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.shareLink)
|
||||
|
|
@ -870,6 +760,10 @@ export default {
|
|||
// YET. We can safely delete the share :)
|
||||
this.$emit('remove:share', this.share)
|
||||
},
|
||||
|
||||
toggleQuickShareSelect() {
|
||||
this.showDropdown = !this.showDropdown
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -879,13 +773,13 @@ export default {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
|
||||
&__desc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
line-height: 1.2em;
|
||||
overflow: hidden;
|
||||
|
||||
p {
|
||||
color: var(--color-text-maxcontrast);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<div :class="{ 'active': showDropdown, 'share-select': true }">
|
||||
<span class="trigger-text" @click="toggleDropdown">
|
||||
{{ selectedOption }}
|
||||
<DropdownIcon :size="15" />
|
||||
</span>
|
||||
<div v-if="showDropdown" class="share-select-dropdown-container">
|
||||
<div v-for="option in options"
|
||||
:key="option"
|
||||
:class="{ 'dropdown-item': true, 'selected': option === selectedOption }"
|
||||
@click="selectOption(option)">
|
||||
{{ option }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DropdownIcon from 'vue-material-design-icons/TriangleSmallDown.vue'
|
||||
import SharesMixin from '../mixins/SharesMixin.js'
|
||||
import ShareDetails from '../mixins/ShareDetails.js'
|
||||
import ShareTypes from '../mixins/ShareTypes.js'
|
||||
|
||||
import {
|
||||
BUNDLED_PERMISSIONS,
|
||||
ATOMIC_PERMISSIONS,
|
||||
} from '../lib/SharePermissionsToolBox.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DropdownIcon,
|
||||
},
|
||||
mixins: [SharesMixin, ShareDetails, ShareTypes],
|
||||
props: {
|
||||
share: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
toggle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedOption: '',
|
||||
showDropdown: this.toggle,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canViewText() {
|
||||
return t('files_sharing', 'View only')
|
||||
},
|
||||
canEditText() {
|
||||
return t('files_sharing', 'Can edit')
|
||||
},
|
||||
fileDropText() {
|
||||
return t('files_sharing', 'File drop')
|
||||
},
|
||||
customPermissionsText() {
|
||||
return t('files_sharing', 'Custom permissions')
|
||||
},
|
||||
preSelectedOption() {
|
||||
// We remove the share permission for the comparison as it is not relevant for bundled permissions.
|
||||
if ((this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === BUNDLED_PERMISSIONS.READ_ONLY) {
|
||||
return this.canViewText
|
||||
} else if (this.share.permissions === BUNDLED_PERMISSIONS.ALL || this.share.permissions === BUNDLED_PERMISSIONS.ALL_FILE) {
|
||||
return this.canEditText
|
||||
} else if ((this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === BUNDLED_PERMISSIONS.FILE_DROP) {
|
||||
return this.fileDropText
|
||||
}
|
||||
|
||||
return this.customPermissionsText
|
||||
|
||||
},
|
||||
options() {
|
||||
const options = [this.canViewText, this.canEditText]
|
||||
if (this.supportsFileDrop) {
|
||||
options.push(this.fileDropText)
|
||||
}
|
||||
options.push(this.customPermissionsText)
|
||||
|
||||
return options
|
||||
},
|
||||
supportsFileDrop() {
|
||||
if (this.isFolder) {
|
||||
const shareType = this.share.type ?? this.share.shareType
|
||||
return [this.SHARE_TYPES.SHARE_TYPE_LINK, this.SHARE_TYPES.SHARE_TYPE_EMAIL].includes(shareType)
|
||||
}
|
||||
return false
|
||||
},
|
||||
dropDownPermissionValue() {
|
||||
switch (this.selectedOption) {
|
||||
case this.canEditText:
|
||||
return this.isFolder ? BUNDLED_PERMISSIONS.ALL : BUNDLED_PERMISSIONS.ALL_FILE
|
||||
case this.fileDropText:
|
||||
return BUNDLED_PERMISSIONS.FILE_DROP
|
||||
case this.customPermissionsText:
|
||||
return 'custom'
|
||||
case this.canViewText:
|
||||
default:
|
||||
return BUNDLED_PERMISSIONS.READ_ONLY
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
toggle(toggleValue) {
|
||||
this.showDropdown = toggleValue
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initializeComponent()
|
||||
},
|
||||
methods: {
|
||||
toggleDropdown() {
|
||||
this.showDropdown = !this.showDropdown
|
||||
},
|
||||
selectOption(option) {
|
||||
this.selectedOption = option
|
||||
if (option === this.customPermissionsText) {
|
||||
this.$emit('open-sharing-details')
|
||||
} else {
|
||||
this.share.permissions = this.dropDownPermissionValue
|
||||
this.queueUpdate('permissions')
|
||||
}
|
||||
this.showDropdown = false
|
||||
},
|
||||
initializeComponent() {
|
||||
this.selectedOption = this.preSelectedOption
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.share-select {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.trigger-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 12.5px;
|
||||
gap: 2px;
|
||||
color: var(--color-primary-element);
|
||||
}
|
||||
|
||||
.share-select-dropdown-container {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: var(--color-main-background);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
padding: 4px 0;
|
||||
z-index: 1;
|
||||
|
||||
.dropdown-item {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional: Add a transition effect for smoother dropdown animation */
|
||||
.share-select-dropdown-container {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
&.active .share-select-dropdown-container {
|
||||
max-height: 200px;
|
||||
/* Adjust the value to your desired height */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -29,8 +29,8 @@
|
|||
{{ subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
<NcActions ref="actionsComponent"
|
||||
v-if="$slots['default']"
|
||||
<NcActions v-if="$slots['default']"
|
||||
ref="actionsComponent"
|
||||
class="sharing-entry__actions"
|
||||
menu-align="right"
|
||||
:aria-expanded="ariaExpandedValue">
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<div class="sharing-search">
|
||||
<label for="sharing-search-input">{{ t('files_sharing', 'Search for share recipients') }}</label>
|
||||
<NcSelect ref="select"
|
||||
v-model="value"
|
||||
input-id="sharing-search-input"
|
||||
class="sharing-search__input"
|
||||
:disabled="!canReshare"
|
||||
|
|
@ -33,10 +34,9 @@
|
|||
:clear-search-on-blur="() => false"
|
||||
:user-select="true"
|
||||
:options="options"
|
||||
v-model="value"
|
||||
@open="handleOpen"
|
||||
@search="asyncFind"
|
||||
@option:selected="addShare">
|
||||
@option:selected="openSharingDetails">
|
||||
<template #no-options="{ search }">
|
||||
{{ search ? noResultText : t('files_sharing', 'No recommendations. Start typing.') }}
|
||||
</template>
|
||||
|
|
@ -57,6 +57,7 @@ import GeneratePassword from '../utils/GeneratePassword.js'
|
|||
import Share from '../models/Share.js'
|
||||
import ShareRequests from '../mixins/ShareRequests.js'
|
||||
import ShareTypes from '../mixins/ShareTypes.js'
|
||||
import ShareDetails from '../mixins/ShareDetails.js'
|
||||
|
||||
export default {
|
||||
name: 'SharingInput',
|
||||
|
|
@ -65,7 +66,7 @@ export default {
|
|||
NcSelect,
|
||||
},
|
||||
|
||||
mixins: [ShareTypes, ShareRequests],
|
||||
mixins: [ShareTypes, ShareRequests, ShareDetails],
|
||||
|
||||
props: {
|
||||
shares: {
|
||||
|
|
@ -176,7 +177,7 @@ export default {
|
|||
* Get suggestions
|
||||
*
|
||||
* @param {string} search the search query
|
||||
* @param {boolean} [lookup=false] search on lookup server
|
||||
* @param {boolean} [lookup] search on lookup server
|
||||
*/
|
||||
async getSuggestions(search, lookup = false) {
|
||||
this.loading = true
|
||||
|
|
@ -452,7 +453,6 @@ export default {
|
|||
}
|
||||
|
||||
return {
|
||||
id: `${result.value.shareType}-${result.value.shareWith}`,
|
||||
shareWith: result.value.shareWith,
|
||||
shareType: result.value.shareType,
|
||||
user: result.uuid || result.value.shareWith,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const BUNDLED_PERMISSIONS = {
|
|||
UPLOAD_AND_UPDATE: ATOMIC_PERMISSIONS.READ | ATOMIC_PERMISSIONS.UPDATE | ATOMIC_PERMISSIONS.CREATE | ATOMIC_PERMISSIONS.DELETE,
|
||||
FILE_DROP: ATOMIC_PERMISSIONS.CREATE,
|
||||
ALL: ATOMIC_PERMISSIONS.UPDATE | ATOMIC_PERMISSIONS.CREATE | ATOMIC_PERMISSIONS.READ | ATOMIC_PERMISSIONS.DELETE | ATOMIC_PERMISSIONS.SHARE,
|
||||
ALL_FILE: ATOMIC_PERMISSIONS.UPDATE | ATOMIC_PERMISSIONS.READ | ATOMIC_PERMISSIONS.SHARE,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
43
apps/files_sharing/src/mixins/ShareDetails.js
Normal file
43
apps/files_sharing/src/mixins/ShareDetails.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import Share from '../models/Share.js'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
openSharingDetails(share) {
|
||||
const shareRequestObject = {
|
||||
fileInfo: this.fileInfo,
|
||||
share: this.mapShareRequestToShareObject(share),
|
||||
}
|
||||
this.$emit('open-sharing-details', shareRequestObject)
|
||||
},
|
||||
openShareDetailsForCustomSettings(share) {
|
||||
share.setCustomPermissions = true
|
||||
this.openSharingDetails(share)
|
||||
},
|
||||
mapShareRequestToShareObject(shareRequestObject) {
|
||||
|
||||
if (shareRequestObject.id) {
|
||||
return shareRequestObject
|
||||
}
|
||||
|
||||
const share = {
|
||||
attributes: [
|
||||
{
|
||||
enabled: true,
|
||||
key: 'download',
|
||||
scope: 'permissions',
|
||||
},
|
||||
],
|
||||
share_type: shareRequestObject.shareType,
|
||||
share_with: shareRequestObject.shareWith,
|
||||
is_no_user: shareRequestObject.isNoUser,
|
||||
user: shareRequestObject.shareWith,
|
||||
share_with_displayname: shareRequestObject.displayName,
|
||||
subtitle: shareRequestObject.subtitle,
|
||||
permissions: shareRequestObject.permissions,
|
||||
expiration: '',
|
||||
}
|
||||
|
||||
return new Share(share)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -42,19 +42,20 @@ export default {
|
|||
* @param {string} data.path path to the file/folder which should be shared
|
||||
* @param {number} data.shareType 0 = user; 1 = group; 3 = public link; 6 = federated cloud share
|
||||
* @param {string} data.shareWith user/group id with which the file should be shared (optional for shareType > 1)
|
||||
* @param {boolean} [data.publicUpload=false] allow public upload to a public shared folder
|
||||
* @param {boolean} [data.publicUpload] allow public upload to a public shared folder
|
||||
* @param {string} [data.password] password to protect public link Share with
|
||||
* @param {number} [data.permissions=31] 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all (default: 31, for public shares: 1)
|
||||
* @param {boolean} [data.sendPasswordByTalk=false] send the password via a talk conversation
|
||||
* @param {string} [data.expireDate=''] expire the shareautomatically after
|
||||
* @param {string} [data.label=''] custom label
|
||||
* @param {string} [data.attributes=null] Share attributes encoded as json
|
||||
* @param {number} [data.permissions] 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all (default: 31, for public shares: 1)
|
||||
* @param {boolean} [data.sendPasswordByTalk] send the password via a talk conversation
|
||||
* @param {string} [data.expireDate] expire the shareautomatically after
|
||||
* @param {string} [data.label] custom label
|
||||
* @param {string} [data.attributes] Share attributes encoded as json
|
||||
* @param data.note
|
||||
* @return {Share} the new share
|
||||
* @throws {Error}
|
||||
*/
|
||||
async createShare({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, attributes }) {
|
||||
async createShare({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, note, attributes }) {
|
||||
try {
|
||||
const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, attributes })
|
||||
const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label, note, attributes })
|
||||
if (!request?.data?.ocs) {
|
||||
throw request
|
||||
}
|
||||
|
|
@ -66,7 +67,7 @@ export default {
|
|||
const errorMessage = error?.response?.data?.ocs?.meta?.message
|
||||
OC.Notification.showTemporary(
|
||||
errorMessage ? t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) : t('files_sharing', 'Error creating the share'),
|
||||
{ type: 'error' }
|
||||
{ type: 'error' },
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
|
@ -91,7 +92,7 @@ export default {
|
|||
const errorMessage = error?.response?.data?.ocs?.meta?.message
|
||||
OC.Notification.showTemporary(
|
||||
errorMessage ? t('files_sharing', 'Error deleting the share: {errorMessage}', { errorMessage }) : t('files_sharing', 'Error deleting the share'),
|
||||
{ type: 'error' }
|
||||
{ type: 'error' },
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
|
@ -118,7 +119,7 @@ export default {
|
|||
const errorMessage = error?.response?.data?.ocs?.meta?.message
|
||||
OC.Notification.showTemporary(
|
||||
errorMessage ? t('files_sharing', 'Error updating the share: {errorMessage}', { errorMessage }) : t('files_sharing', 'Error updating the share'),
|
||||
{ type: 'error' }
|
||||
{ type: 'error' },
|
||||
)
|
||||
}
|
||||
const message = error.response.data.ocs.meta.message
|
||||
|
|
|
|||
|
|
@ -36,13 +36,17 @@ import SharesRequests from './ShareRequests.js'
|
|||
import ShareTypes from './ShareTypes.js'
|
||||
import Config from '../services/ConfigService.js'
|
||||
|
||||
import {
|
||||
BUNDLED_PERMISSIONS,
|
||||
} from '../lib/SharePermissionsToolBox.js'
|
||||
|
||||
export default {
|
||||
mixins: [SharesRequests, ShareTypes],
|
||||
|
||||
props: {
|
||||
fileInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => { },
|
||||
required: true,
|
||||
},
|
||||
share: {
|
||||
|
|
@ -121,11 +125,24 @@ export default {
|
|||
monthFormat: 'MMM',
|
||||
}
|
||||
},
|
||||
|
||||
isFolder() {
|
||||
return this.fileInfo.type === 'dir'
|
||||
},
|
||||
isPublicShare() {
|
||||
const shareType = this.share.shareType ?? this.share.type
|
||||
return [this.SHARE_TYPES.SHARE_TYPE_LINK, this.SHARE_TYPES.SHARE_TYPE_EMAIL].includes(shareType)
|
||||
},
|
||||
isShareOwner() {
|
||||
return this.share && this.share.owner === getCurrentUser().uid
|
||||
},
|
||||
|
||||
hasCustomPermissions() {
|
||||
const bundledPermissions = [
|
||||
BUNDLED_PERMISSIONS.ALL,
|
||||
BUNDLED_PERMISSIONS.READ_ONLY,
|
||||
BUNDLED_PERMISSIONS.FILE_DROP,
|
||||
]
|
||||
return !bundledPermissions.includes(this.share.permissions)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -180,8 +197,7 @@ export default {
|
|||
* @param {Date} date
|
||||
*/
|
||||
onExpirationChange(date) {
|
||||
this.share.expireDate = this.formatDateToString(date)
|
||||
this.queueUpdate('expireDate')
|
||||
this.share.expireDate = this.formatDateToString(new Date(date))
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -192,7 +208,6 @@ export default {
|
|||
*/
|
||||
onExpirationDisable() {
|
||||
this.share.expireDate = ''
|
||||
this.queueUpdate('expireDate')
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -335,7 +350,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Debounce queueUpdate to avoid requests spamming
|
||||
* more importantly for text data
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@ export default class Share {
|
|||
for (const i in this._share.attributes) {
|
||||
const attr = this._share.attributes[i]
|
||||
if (attr.scope === attrUpdate.scope && attr.key === attrUpdate.key) {
|
||||
this._share.attributes[i] = attrUpdate
|
||||
this._share.attributes.splice(i, 1, attrUpdate)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const shareWithTitle = function(share) {
|
|||
owner: share.ownerDisplayName,
|
||||
},
|
||||
undefined,
|
||||
{ escape: false }
|
||||
{ escape: false },
|
||||
)
|
||||
} else if (share.type === ShareTypes.SHARE_TYPE_CIRCLE) {
|
||||
return t(
|
||||
|
|
@ -44,7 +44,7 @@ const shareWithTitle = function(share) {
|
|||
owner: share.ownerDisplayName,
|
||||
},
|
||||
undefined,
|
||||
{ escape: false }
|
||||
{ escape: false },
|
||||
)
|
||||
} else if (share.type === ShareTypes.SHARE_TYPE_ROOM) {
|
||||
if (share.shareWithDisplayName) {
|
||||
|
|
@ -56,7 +56,7 @@ const shareWithTitle = function(share) {
|
|||
owner: share.ownerDisplayName,
|
||||
},
|
||||
undefined,
|
||||
{ escape: false }
|
||||
{ escape: false },
|
||||
)
|
||||
} else {
|
||||
return t(
|
||||
|
|
@ -66,7 +66,7 @@ const shareWithTitle = function(share) {
|
|||
owner: share.ownerDisplayName,
|
||||
},
|
||||
undefined,
|
||||
{ escape: false }
|
||||
{ escape: false },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -75,7 +75,7 @@ const shareWithTitle = function(share) {
|
|||
'Shared with you by {owner}',
|
||||
{ owner: share.ownerDisplayName },
|
||||
undefined,
|
||||
{ escape: false }
|
||||
{ escape: false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1053
apps/files_sharing/src/views/SharingDetailsTab.vue
Normal file
1053
apps/files_sharing/src/views/SharingDetailsTab.vue
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -39,7 +39,8 @@
|
|||
:file-info="fileInfo"
|
||||
@add:share="addShare(...arguments)"
|
||||
@update:share="awaitForShare(...arguments)"
|
||||
@remove:share="removeShare" />
|
||||
@remove:share="removeShare"
|
||||
@open-sharing-details="openSharingDetails(share)" />
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
import Share from '../models/Share.js'
|
||||
import ShareTypes from '../mixins/ShareTypes.js'
|
||||
import SharingEntryLink from '../components/SharingEntryLink.vue'
|
||||
import ShareDetails from '../mixins/ShareDetails.js'
|
||||
|
||||
export default {
|
||||
name: 'SharingLinkList',
|
||||
|
|
@ -57,7 +59,7 @@ export default {
|
|||
SharingEntryLink,
|
||||
},
|
||||
|
||||
mixins: [ShareTypes],
|
||||
mixins: [ShareTypes, ShareDetails],
|
||||
|
||||
props: {
|
||||
fileInfo: {
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@
|
|||
:file-info="fileInfo"
|
||||
:share="share"
|
||||
:is-unique="isUnique(share)"
|
||||
@remove:share="removeShare" />
|
||||
@open-sharing-details="openSharingDetails(share)" />
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import Share from '../models/Share.js'
|
||||
import SharingEntry from '../components/SharingEntry.vue'
|
||||
import ShareTypes from '../mixins/ShareTypes.js'
|
||||
import ShareDetails from '../mixins/ShareDetails.js'
|
||||
|
||||
export default {
|
||||
name: 'SharingList',
|
||||
|
|
@ -44,12 +44,12 @@ export default {
|
|||
SharingEntry,
|
||||
},
|
||||
|
||||
mixins: [ShareTypes],
|
||||
mixins: [ShareTypes, ShareDetails],
|
||||
|
||||
props: {
|
||||
fileInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => { },
|
||||
required: true,
|
||||
},
|
||||
shares: {
|
||||
|
|
@ -58,7 +58,6 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasShares() {
|
||||
return this.shares.length === 0
|
||||
|
|
@ -71,18 +70,5 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Remove a share from the shares list
|
||||
*
|
||||
* @param {Share} share the share to remove
|
||||
*/
|
||||
removeShare(share) {
|
||||
const index = this.shares.findIndex(item => item === share)
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.shares.splice(index, 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
|
||||
<!-- shares content -->
|
||||
<div v-else class="sharingTab__content">
|
||||
<div v-if="!showSharingDetailsView" class="sharingTab__content">
|
||||
<!-- shared with me information -->
|
||||
<SharingEntrySimple v-if="isSharedWithMe" v-bind="sharedWithMe" class="sharing-entry__reshare">
|
||||
<template #avatar>
|
||||
|
|
@ -46,20 +46,22 @@
|
|||
:link-shares="linkShares"
|
||||
:reshare="reshare"
|
||||
:shares="shares"
|
||||
@add:share="addShare" />
|
||||
@open-sharing-details="toggleShareDetailsView" />
|
||||
|
||||
<!-- link shares list -->
|
||||
<SharingLinkList v-if="!loading"
|
||||
ref="linkShareList"
|
||||
:can-reshare="canReshare"
|
||||
:file-info="fileInfo"
|
||||
:shares="linkShares" />
|
||||
:shares="linkShares"
|
||||
@open-sharing-details="toggleShareDetailsView" />
|
||||
|
||||
<!-- other shares list -->
|
||||
<SharingList v-if="!loading"
|
||||
ref="shareList"
|
||||
:shares="shares"
|
||||
:file-info="fileInfo" />
|
||||
:file-info="fileInfo"
|
||||
@open-sharing-details="toggleShareDetailsView" />
|
||||
|
||||
<!-- inherited shares -->
|
||||
<SharingInherited v-if="canReshare && !loading" :file-info="fileInfo" />
|
||||
|
|
@ -74,6 +76,15 @@
|
|||
:name="fileInfo.name" />
|
||||
</div>
|
||||
|
||||
<!-- share details -->
|
||||
<div v-else>
|
||||
<SharingDetailsTab :file-info="shareDetailsData.fileInfo"
|
||||
:share="shareDetailsData.share"
|
||||
@close-sharing-details="toggleShareDetailsView"
|
||||
@add:share="addShare"
|
||||
@remove:share="removeShare" />
|
||||
</div>
|
||||
|
||||
<!-- additional entries, use it with cautious -->
|
||||
<div v-for="(section, index) in sections"
|
||||
:ref="'section-' + index"
|
||||
|
|
@ -102,6 +113,7 @@ import SharingInput from '../components/SharingInput.vue'
|
|||
import SharingInherited from './SharingInherited.vue'
|
||||
import SharingLinkList from './SharingLinkList.vue'
|
||||
import SharingList from './SharingList.vue'
|
||||
import SharingDetailsTab from './SharingDetailsTab.vue'
|
||||
|
||||
export default {
|
||||
name: 'SharingTab',
|
||||
|
|
@ -115,6 +127,7 @@ export default {
|
|||
SharingInput,
|
||||
SharingLinkList,
|
||||
SharingList,
|
||||
SharingDetailsTab,
|
||||
},
|
||||
|
||||
mixins: [ShareTypes],
|
||||
|
|
@ -122,7 +135,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
config: new Config(),
|
||||
|
||||
deleteEvent: null,
|
||||
error: '',
|
||||
expirationInterval: null,
|
||||
loading: true,
|
||||
|
|
@ -137,6 +150,8 @@ export default {
|
|||
|
||||
sections: OCA.Sharing.ShareTabSections.getSections(),
|
||||
projectsEnabled: loadState('core', 'projects_enabled', false),
|
||||
showSharingDetailsView: false,
|
||||
shareDetailsData: {},
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -225,6 +240,8 @@ export default {
|
|||
this.sharedWithMe = {}
|
||||
this.shares = []
|
||||
this.linkShares = []
|
||||
this.showSharingDetailsView = false
|
||||
this.shareDetailsData = {}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -307,7 +324,7 @@ export default {
|
|||
'Shared with you by {owner}',
|
||||
{ owner: this.fileInfo.shareOwner },
|
||||
undefined,
|
||||
{ escape: false }
|
||||
{ escape: false },
|
||||
),
|
||||
user: this.fileInfo.shareOwnerId,
|
||||
}
|
||||
|
|
@ -321,7 +338,7 @@ export default {
|
|||
* @param {Share} share the share to add to the array
|
||||
* @param {Function} [resolve] a function to run after the share is added and its component initialized
|
||||
*/
|
||||
addShare(share, resolve = () => {}) {
|
||||
addShare(share, resolve = () => { }) {
|
||||
// only catching share type MAIL as link shares are added differently
|
||||
// meaning: not from the ShareInput
|
||||
if (share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL) {
|
||||
|
|
@ -331,7 +348,16 @@ export default {
|
|||
}
|
||||
this.awaitForShare(share, resolve)
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a share from the shares list
|
||||
*
|
||||
* @param {Share} share the share to remove
|
||||
*/
|
||||
removeShare(share) {
|
||||
const index = this.shares.findIndex(item => item.id === share.id)
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.shares.splice(index, 1)
|
||||
},
|
||||
/**
|
||||
* Await for next tick and render after the list updated
|
||||
* Then resolve with the matched vue component of the
|
||||
|
|
@ -355,6 +381,12 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
toggleShareDetailsView(eventData) {
|
||||
if (eventData) {
|
||||
this.shareDetailsData = eventData
|
||||
}
|
||||
this.showSharingDetailsView = !this.showSharingDetailsView
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -368,6 +400,7 @@ export default {
|
|||
&__content {
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
&__additionalContent {
|
||||
margin: 44px 0;
|
||||
}
|
||||
|
|
|
|||
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.LICENSE.txt
vendored
2
dist/core-common.js.LICENSE.txt
vendored
|
|
@ -372,8 +372,6 @@ object-assign
|
|||
|
||||
/*! For license information please see NcActionText.js.LICENSE.txt */
|
||||
|
||||
/*! For license information please see NcActionTextEditable.js.LICENSE.txt */
|
||||
|
||||
/*! For license information please see NcActions.js.LICENSE.txt */
|
||||
|
||||
/*! For license information please see NcAppContent.js.LICENSE.txt */
|
||||
|
|
|
|||
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue