You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nextcloud/apps/files_sharing/src/components/SharingEntry.vue

480 lines
14 KiB
Vue

<!--
- @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<li class="sharing-entry">
<NcAvatar class="sharing-entry__avatar"
:is-no-user="share.type !== SHARE_TYPES.SHARE_TYPE_USER"
:user="share.shareWith"
:display-name="share.shareWithDisplayName"
: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>
</template>
<NcActionButton v-if="share.canDelete"
icon="icon-close"
:disabled="saving"
@click.prevent="onDelete">
{{ t('files_sharing', 'Unshare') }}
</NcActionButton>
</NcActions>
</li>
</template>
<script>
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 SharesMixin from '../mixins/SharesMixin.js'
export default {
name: 'SharingEntry',
components: {
NcActions,
NcActionButton,
NcActionCheckbox,
NcActionInput,
NcActionTextEditable,
NcAvatar,
},
mixins: [SharesMixin],
data() {
return {
permissionsEdit: OC.PERMISSION_UPDATE,
permissionsCreate: OC.PERMISSION_CREATE,
permissionsDelete: OC.PERMISSION_DELETE,
permissionsRead: OC.PERMISSION_READ,
permissionsShare: OC.PERMISSION_SHARE,
}
},
computed: {
title() {
let title = this.share.shareWithDisplayName
if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
title += ` (${t('files_sharing', 'group')})`
} else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_ROOM) {
title += ` (${t('files_sharing', 'conversation')})`
} else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE) {
title += ` (${t('files_sharing', 'remote')})`
} else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP) {
title += ` (${t('files_sharing', 'remote group')})`
} else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GUEST) {
title += ` (${t('files_sharing', 'guest')})`
}
return title
},
tooltip() {
if (this.share.owner !== this.share.uidFileOwner) {
const data = {
// todo: strong or italic?
// but the t function escape any html from the data :/
user: this.share.shareWithDisplayName,
owner: this.share.ownerDisplayName,
}
if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
return t('files_sharing', 'Shared with the group {user} by {owner}', data)
} else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_ROOM) {
return t('files_sharing', 'Shared with the conversation {user} by {owner}', data)
}
return t('files_sharing', 'Shared with {user} by {owner}', data)
}
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}
*/
hasStatus() {
if (this.share.type !== this.SHARE_TYPES.SHARE_TYPE_USER) {
return false
}
return (typeof this.share.status === 'object' && !Array.isArray(this.share.status))
},
/**
* @return {string}
*/
allowDownloadText() {
return t('files_sharing', 'Allow download')
},
/**
* @return {boolean}
*/
isSetDownloadButtonVisible() {
const allowedMimetypes = [
// Office documents
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.presentation',
]
return this.isFolder || allowedMimetypes.includes(this.fileInfo.mimetype)
},
},
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()
},
},
}
</script>
<style lang="scss" scoped>
.sharing-entry {
display: flex;
align-items: center;
height: 44px;
&__desc {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 8px;
line-height: 1.2em;
p {
color: var(--color-text-maxcontrast);
}
&-unique {
color: var(--color-text-maxcontrast);
}
}
&__actions {
margin-left: auto;
}
}
</style>