mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04: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
db1a7ba03a
commit
1d74d62d2a
25 changed files with 1474 additions and 2170 deletions
60
.drone.yml
60
.drone.yml
|
|
@ -1621,66 +1621,6 @@ trigger:
|
|||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: acceptance-app-files-sharing
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: ghcr.io/nextcloud/continuous-integration-alpine-git:latest
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: acceptance-app-files-sharing
|
||||
image: ghcr.io/nextcloud/continuous-integration-acceptance-php8.0:latest
|
||||
commands:
|
||||
- tests/acceptance/run-local.sh --timeout-multiplier 10 --nextcloud-server-domain acceptance-app-files-sharing --selenium-server selenium:4444 allow-git-repository-modifications features/app-files-sharing.feature
|
||||
|
||||
services:
|
||||
- name: selenium
|
||||
image: ghcr.io/nextcloud/continuous-integration-selenium:3.141.59
|
||||
environment:
|
||||
# Reduce default log level for Selenium server (INFO) as it is too
|
||||
# verbose.
|
||||
JAVA_OPTS: -Dselenium.LOGGER.level=WARNING
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: acceptance-app-files-sharing-link
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: ghcr.io/nextcloud/continuous-integration-alpine-git:latest
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- name: acceptance-app-files-sharing-link
|
||||
image: ghcr.io/nextcloud/continuous-integration-acceptance-php8.0:latest
|
||||
commands:
|
||||
- tests/acceptance/run-local.sh --timeout-multiplier 10 --nextcloud-server-domain acceptance-app-files-sharing-link --selenium-server selenium:4444 allow-git-repository-modifications features/app-files-sharing-link.feature
|
||||
|
||||
services:
|
||||
- name: selenium
|
||||
image: ghcr.io/nextcloud/continuous-integration-selenium:3.141.59
|
||||
environment:
|
||||
# Reduce default log level for Selenium server (INFO) as it is too
|
||||
# verbose.
|
||||
JAVA_OPTS: -Dselenium.LOGGER.level=WARNING
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- stable*
|
||||
event:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: acceptance-app-files-tags
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -17,7 +17,6 @@ default:
|
|||
- FileListContext
|
||||
- FilePickerContext
|
||||
- FilesAppContext
|
||||
- FilesAppSharingContext
|
||||
- LoginPageContext
|
||||
- NotificationsContext
|
||||
- PublicShareContext
|
||||
|
|
@ -47,7 +46,6 @@ default:
|
|||
- FileListContext
|
||||
- FilePickerContext
|
||||
- FilesAppContext
|
||||
- FilesAppSharingContext
|
||||
- LoginPageContext
|
||||
- NotificationsContext
|
||||
- PublicShareContext
|
||||
|
|
|
|||
|
|
@ -1,250 +0,0 @@
|
|||
Feature: app-files-sharing-link
|
||||
|
||||
Scenario: open the menu in a public shared link
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I share the link for "welcome.txt"
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I see that the current page is the shared link I wrote down
|
||||
And I open the Share menu
|
||||
Then I see that the Share menu is shown
|
||||
|
||||
# TODO: disabled unreliable test
|
||||
# Scenario: hide download in a public shared link
|
||||
# Given I act as John
|
||||
# And I am logged in
|
||||
# And I share the link for "welcome.txt"
|
||||
# And I set the download of the shared link as hidden
|
||||
# And I write down the shared link
|
||||
# When I act as Jane
|
||||
# And I visit the shared link I wrote down
|
||||
# And I see that the current page is the shared link I wrote down
|
||||
# Then I see that the download button is not shown
|
||||
# And I see that the Share menu button is not shown
|
||||
|
||||
# TODO: disabled unreliable test
|
||||
# Scenario: show download again in a public shared link
|
||||
# Given I act as John
|
||||
# And I am logged in
|
||||
# And I share the link for "welcome.txt"
|
||||
# And I set the download of the shared link as hidden
|
||||
# And I set the download of the shared link as shown
|
||||
# And I write down the shared link
|
||||
# When I act as Jane
|
||||
# And I visit the shared link I wrote down
|
||||
# And I see that the current page is the shared link I wrote down
|
||||
# Then I see that the download button is shown
|
||||
# And I open the Share menu
|
||||
# And I see that the Share menu is shown
|
||||
|
||||
Scenario: open a subfolder in a public shared folder
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I create a new folder named "Shared folder with subfolders"
|
||||
And I enter in the folder named "Shared folder with subfolders"
|
||||
And I create a new folder named "Subfolder"
|
||||
And I enter in the folder named "Subfolder"
|
||||
And I create a new folder named "Subsubfolder"
|
||||
And I see that the file list contains a file named "Subsubfolder"
|
||||
# The Files app is open again to reload the file list
|
||||
And I open the Files app
|
||||
And I share the link for "Shared folder with subfolders"
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I see that the current page is the shared link I wrote down
|
||||
Then I see that the file list contains a file named "Subfolder"
|
||||
And I enter in the folder named "Subfolder"
|
||||
And I see that the file list contains a file named "Subsubfolder"
|
||||
|
||||
Scenario: creation is not possible by default in a public shared folder
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I create a new folder named "Shared folder"
|
||||
# To share the link the "Share" inline action has to be clicked but, as the
|
||||
# details view is opened automatically when the folder is created, clicking
|
||||
# on the inline action could fail if it is covered by the details view due
|
||||
# to its opening animation. Instead of ensuring that the animations of the
|
||||
# contents and the details view have both finished it is easier to close the
|
||||
# details view and wait until it is closed before continuing.
|
||||
And I close the details view
|
||||
And I see that the details view is closed
|
||||
And I share the link for "Shared folder"
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I see that the current page is the shared link I wrote down
|
||||
And I see that the file list is eventually loaded
|
||||
Then I see that it is not possible to create new files
|
||||
|
||||
Scenario: create folder in a public editable shared folder
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I create a new folder named "Editable shared folder"
|
||||
# To share the link the "Share" inline action has to be clicked but, as the
|
||||
# details view is opened automatically when the folder is created, clicking
|
||||
# on the inline action could fail if it is covered by the details view due
|
||||
# to its opening animation. Instead of ensuring that the animations of the
|
||||
# contents and the details view have both finished it is easier to close the
|
||||
# details view and wait until it is closed before continuing.
|
||||
And I close the details view
|
||||
And I see that the details view is closed
|
||||
And I share the link for "Editable shared folder"
|
||||
And I set the shared link as editable
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I see that the current page is the shared link I wrote down
|
||||
And I create a new folder named "Subfolder"
|
||||
Then I see that the file list contains a file named "Subfolder"
|
||||
|
||||
Scenario: owner sees folder created in the public page of an editable shared folder
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I create a new folder named "Editable shared folder"
|
||||
# To share the link the "Share" inline action has to be clicked but, as the
|
||||
# details view is opened automatically when the folder is created, clicking
|
||||
# on the inline action could fail if it is covered by the details view due
|
||||
# to its opening animation. Instead of ensuring that the animations of the
|
||||
# contents and the details view have both finished it is easier to close the
|
||||
# details view and wait until it is closed before continuing.
|
||||
And I close the details view
|
||||
And I see that the details view is closed
|
||||
And I share the link for "Editable shared folder"
|
||||
And I set the shared link as editable
|
||||
And I write down the shared link
|
||||
And I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I see that the current page is the shared link I wrote down
|
||||
And I create a new folder named "Subfolder"
|
||||
And I see that the file list contains a file named "Subfolder"
|
||||
When I act as John
|
||||
And I enter in the folder named "Editable shared folder"
|
||||
Then I see that the file list contains a file named "Subfolder"
|
||||
|
||||
Scenario: set a password to a shared link
|
||||
Given I am logged in
|
||||
And I share the link for "welcome.txt"
|
||||
When I protect the shared link with the password "abcdef"
|
||||
Then I see that the password protect is disabled while loading
|
||||
And I see that the link share is password protected
|
||||
# As Talk is not enabled in the acceptance tests of the server the checkbox
|
||||
# is never shown.
|
||||
And I see that the checkbox to protect the password of the link share by Talk is not shown
|
||||
|
||||
Scenario: access a shared link protected by password with a valid password
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I share the link for "welcome.txt" protected by the password "abcdef"
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I see that the current page is the Authenticate page for the shared link I wrote down
|
||||
And I authenticate with password "abcdef"
|
||||
Then I see that the current page is the shared link I wrote down
|
||||
And I see that the shared file preview shows the text "Welcome to your Nextcloud account!"
|
||||
|
||||
Scenario: access a shared link protected by password with an invalid password
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I share the link for "welcome.txt" protected by the password "abcdef"
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the shared link I wrote down
|
||||
And I authenticate with password "fedcba"
|
||||
Then I see that the current page is the Authenticate page for the shared link I wrote down
|
||||
And I see that a wrong password for the shared file message is shown
|
||||
|
||||
Scenario: access a direct download shared link protected by password with a valid password
|
||||
Given I act as John
|
||||
And I am logged in
|
||||
And I share the link for "welcome.txt" protected by the password "abcdef"
|
||||
And I write down the shared link
|
||||
When I act as Jane
|
||||
And I visit the direct download shared link I wrote down
|
||||
And I see that the current page is the Authenticate page for the direct download shared link I wrote down
|
||||
And I authenticate with password "abcdef"
|
||||
# download starts no page redirection
|
||||
And I see that the current page is the Authenticate page for the direct download shared link I wrote down
|
||||
|
||||
Scenario: sharee can not reshare by link if resharing is disabled in the settings after the share is created
|
||||
Given I act as John
|
||||
And I am logged in as the admin
|
||||
And I act as Jane
|
||||
And I am logged in
|
||||
And I act as John
|
||||
And I rename "welcome.txt" to "farewell.txt"
|
||||
And I see that the file list contains a file named "farewell.txt"
|
||||
And I share "farewell.txt" with "user0"
|
||||
And I see that the file is shared with "user0"
|
||||
And I visit the admin settings page
|
||||
And I open the "Sharing" section of the "Administration" group
|
||||
And I disable resharing
|
||||
And I see that resharing is disabled
|
||||
When I act as Jane
|
||||
# The Files app is open again to reload the file list
|
||||
And I open the Files app
|
||||
Then I see that the file list contains a file named "farewell.txt"
|
||||
And I open the details view for "farewell.txt"
|
||||
And I see that the details view is open
|
||||
And I open the "Sharing" tab in the details view
|
||||
And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
And I see that the file is shared with me by "admin"
|
||||
And I see that resharing the file by link is not available
|
||||
|
||||
Scenario: sharee can unshare a reshare by link if resharing is disabled in the settings after the reshare is created
|
||||
Given I act as John
|
||||
And I am logged in as the admin
|
||||
And I act as Jane
|
||||
And I am logged in
|
||||
And I act as John
|
||||
And I rename "welcome.txt" to "farewell.txt"
|
||||
And I see that the file list contains a file named "farewell.txt"
|
||||
And I share "farewell.txt" with "user0"
|
||||
And I see that the file is shared with "user0"
|
||||
And I act as Jane
|
||||
# The Files app is open again to reload the file list
|
||||
And I open the Files app
|
||||
And I share the link for "farewell.txt"
|
||||
And I write down the shared link
|
||||
And I act as John
|
||||
And I visit the admin settings page
|
||||
And I open the "Sharing" section of the "Administration" group
|
||||
And I disable resharing
|
||||
And I see that resharing is disabled
|
||||
When I act as Jane
|
||||
# The Files app is open again to reload the file list
|
||||
And I open the Files app
|
||||
And I open the details view for "farewell.txt"
|
||||
And I see that the details view is open
|
||||
And I open the "Sharing" tab in the details view
|
||||
And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
And I unshare the link share
|
||||
Then I see that resharing the file by link is not available
|
||||
|
||||
Scenario: reshare by link can be accessed if resharing is disabled in the settings after the reshare is created
|
||||
Given I act as John
|
||||
And I am logged in as the admin
|
||||
And I act as Jane
|
||||
And I am logged in
|
||||
And I act as John
|
||||
And I rename "welcome.txt" to "farewell.txt"
|
||||
And I see that the file list contains a file named "farewell.txt"
|
||||
And I share "farewell.txt" with "user0"
|
||||
And I see that the file is shared with "user0"
|
||||
And I act as Jane
|
||||
# The Files app is open again to reload the file list
|
||||
And I open the Files app
|
||||
And I share the link for "farewell.txt"
|
||||
And I write down the shared link
|
||||
And I act as John
|
||||
And I visit the admin settings page
|
||||
And I open the "Sharing" section of the "Administration" group
|
||||
And I disable resharing
|
||||
And I see that resharing is disabled
|
||||
When I act as Jim
|
||||
And I visit the shared link I wrote down
|
||||
Then I see that the current page is the shared link I wrote down
|
||||
And I see that the shared file preview shows the text "Welcome to your Nextcloud account!"
|
||||
|
|
@ -1,485 +0,0 @@
|
|||
Feature: app-files-sharing
|
||||
|
||||
Scenario: share a file with another user
|
||||
Given I act as John
|
||||
And I am logged in as the admin
|
||||
And I act as Jane
|
||||
And I am logged in
|
||||
And I act as John
|
||||
And I rename "welcome.txt" to "farewell.txt"
|
||||
And I see that the file list contains a file named "farewell.txt"
|
||||
When I share "farewell.txt" with "user0"
|
||||
And I see that the file is shared with "user0"
|
||||
And I act as Jane
|
||||
# The Files app is open again to reload the file list
|
||||
And I open the Files app
|
||||
Then I see that the file list contains a file named "farewell.txt"
|
||||
And I open the details view for "farewell.txt"
|
||||
And I see that the details view is open
|
||||
And I open the "Sharing" tab in the details view
|
||||
And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
And I see that the file is shared with me by "admin"
|
||||
|
||||
# Scenario: share a file with another user that needs to accept shares
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I visit the settings page
|
||||
# And I open the "Sharing" section
|
||||
# And I disable accepting the shares by default
|
||||
# And I see that shares are not accepted by default
|
||||
# And I act as John
|
||||
# And I rename "welcome.txt" to "farewell.txt"
|
||||
# And I see that the file list contains a file named "farewell.txt"
|
||||
# When I share "farewell.txt" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# And I open the Files app
|
||||
# And I see that the file list does not contain a file named "farewell.txt"
|
||||
# And I accept the share for "/farewell.txt" in the notifications
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "farewell.txt"
|
||||
# And I open the details view for "farewell.txt"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
#
|
||||
# Scenario: share a file with another user who already has a file with that name
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# When I share "welcome.txt" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "welcome (2).txt"
|
||||
# And I open the details view for "welcome (2).txt"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
#
|
||||
# Scenario: share a skeleton file with another user before first login
|
||||
# # If a file is shared with a user before her first login the skeleton would
|
||||
# # not have been created, so if the shared file has the same name as one from
|
||||
# # the skeleton the shared file will take its place and the skeleton file
|
||||
# # will not be added.
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# When I share "welcome.txt" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# Then I see that the file list contains a file named "welcome.txt"
|
||||
# And I open the details view for "welcome.txt"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
#
|
||||
# Scenario: reshare a file with another user
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as Jim
|
||||
# And I am logged in as "user1"
|
||||
# And I act as John
|
||||
# And I rename "welcome.txt" to "farewell.txt"
|
||||
# And I see that the file list contains a file named "farewell.txt"
|
||||
# And I share "farewell.txt" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# When I share "farewell.txt" with "user1"
|
||||
# And I see that the file is shared with "user1"
|
||||
# And I act as Jim
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "farewell.txt"
|
||||
# And I open the details view for "farewell.txt"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "user0"
|
||||
#
|
||||
# Scenario: owner sees reshares with other users
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I rename "welcome.txt" to "farewell.txt"
|
||||
# And I see that the file list contains a file named "farewell.txt"
|
||||
# And I share "farewell.txt" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "farewell.txt" with "user1"
|
||||
# And I see that the file is shared with "user1"
|
||||
# When I act as John
|
||||
# # The Files app is open again to reload the file list and the shares
|
||||
# And I open the Files app
|
||||
# And I open the details view for "farewell.txt"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# Then I see that the file is shared with "user0"
|
||||
# And I see that the file is shared with "user1"
|
||||
#
|
||||
# Scenario: share an empty folder with another user
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# When I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "Shared folder"
|
||||
# And I open the details view for "Shared folder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
#
|
||||
# Scenario: sharee sees a folder created by the owner in a shared folder
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# Then I see that the file list contains a file named "Subfolder"
|
||||
#
|
||||
# Scenario: owner sees a folder created by the sharee in a shared folder
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as John
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# Then I see that the file list contains a file named "Subfolder"
|
||||
#
|
||||
# Scenario: resharee sees a folder created by the owner in a shared folder
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as Jim
|
||||
# And I am logged in as "user1"
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "Shared folder" with "user1"
|
||||
# And I act as John
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as Jim
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# Then I see that the file list contains a file named "Subfolder"
|
||||
#
|
||||
# Scenario: owner sees a folder created by the resharee in a shared folder
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as Jim
|
||||
# And I am logged in as "user1"
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "Shared folder" with "user1"
|
||||
# And I act as Jim
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as John
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# Then I see that the file list contains a file named "Subfolder"
|
||||
#
|
||||
# Scenario: sharer does not see resharing option for a folder if resharing is disabled in the settings after the share is created
|
||||
# Given I am logged in as the admin
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# When I visit the admin settings page
|
||||
# And I open the "Sharing" section of the "Administration" group
|
||||
# And I disable resharing
|
||||
# And I see that resharing is disabled
|
||||
# Then I open the Files app
|
||||
# And I open the details view for "Shared folder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that resharing for "user0" is not available
|
||||
#
|
||||
# Scenario: sharee can not reshare a folder if resharing is disabled in the settings after the share is created
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I visit the admin settings page
|
||||
# And I open the "Sharing" section of the "Administration" group
|
||||
# And I disable resharing
|
||||
# And I see that resharing is disabled
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "Shared folder"
|
||||
# And I open the details view for "Shared folder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
# And I see that resharing the file is not allowed
|
||||
#
|
||||
# Scenario: sharee can unshare a folder if resharing is disabled in the settings after the share is created
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "Shared folder" with "user1"
|
||||
# And I act as John
|
||||
# And I visit the admin settings page
|
||||
# And I open the "Sharing" section of the "Administration" group
|
||||
# And I disable resharing
|
||||
# And I see that resharing is disabled
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "Shared folder"
|
||||
# And I open the details view for "Shared folder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
# And I unshare the share with "user1"
|
||||
# And I see that the file is not shared with "user1"
|
||||
#
|
||||
# Scenario: resharee sees a folder created by the owner in a shared folder if resharing is disabled in the settings after the share is created
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as Jim
|
||||
# And I am logged in as "user1"
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "Shared folder" with "user1"
|
||||
# And I act as John
|
||||
# And I visit the admin settings page
|
||||
# And I open the "Sharing" section of the "Administration" group
|
||||
# And I disable resharing
|
||||
# And I see that resharing is disabled
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as Jim
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# Then I see that the file list contains a file named "Subfolder"
|
||||
#
|
||||
# Scenario: sharee can not reshare a folder if the sharer disables it
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I set the share with "user0" as not reshareable
|
||||
# And I see that "user0" can not reshare the share
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# Then I see that the file list contains a file named "Shared folder"
|
||||
# And I open the details view for "Shared folder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that the file is shared with me by "admin"
|
||||
# And I see that resharing the file is not allowed
|
||||
#
|
||||
# Scenario: sharee can not reshare a subfolder if the sharer disables it for the parent folder
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I set the share with "user0" as not reshareable
|
||||
# And I see that "user0" can not reshare the share
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# Then I see that the file list contains a file named "Subfolder"
|
||||
# And I open the details view for "Subfolder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I see that resharing the file is not allowed
|
||||
#
|
||||
# Scenario: sharee can not reshare a file with edit permission if the sharer disables it
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I rename "welcome.txt" to "farewell.txt"
|
||||
# And I see that the file list contains a file named "farewell.txt"
|
||||
# And I share "farewell.txt" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I set the share with "user0" as not editable
|
||||
# And I see that "user0" can not edit the share
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "farewell.txt" with "user1"
|
||||
# Then I see that the file is shared with "user1"
|
||||
# And I see that "user1" can not edit the share
|
||||
# And I see that "user1" can not be allowed to edit the share
|
||||
|
||||
# TODO: disabled unreliable test
|
||||
# Scenario: sharee can not reshare a folder with create permission if the sharer disables it
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I set the share with "user0" as not creatable
|
||||
# And I see that "user0" can not create in the share
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "Shared folder" with "user1"
|
||||
# Then I see that the file is shared with "user1"
|
||||
# And I see that "user1" can not create in the share
|
||||
# And I see that "user1" can not be allowed to create in the share
|
||||
|
||||
# TODO: disabled unreliable test
|
||||
# Scenario: sharee can revoke create permission from reshare after the sharer disabled it
|
||||
# Given I act as John
|
||||
# And I am logged in as the admin
|
||||
# And I act as Jane
|
||||
# And I am logged in
|
||||
# And I act as Jim
|
||||
# And I am logged in as "user1"
|
||||
# And I act as John
|
||||
# And I create a new folder named "Shared folder"
|
||||
# And I see that the file list contains a file named "Shared folder"
|
||||
# And I share "Shared folder" with "user0"
|
||||
# And I see that the file is shared with "user0"
|
||||
# And I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I share "Shared folder" with "user1"
|
||||
# And I see that the file is shared with "user1"
|
||||
# And I act as John
|
||||
# And I set the share with "user0" as not creatable
|
||||
# And I see that "user0" can not create in the share
|
||||
# And I act as Jim
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# # Creation is still allowed in already created reshares
|
||||
# And I create a new folder named "Subfolder"
|
||||
# And I see that the file list contains a file named "Subfolder"
|
||||
# When I act as Jane
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I open the details view for "Shared folder"
|
||||
# And I see that the details view is open
|
||||
# And I open the "Sharing" tab in the details view
|
||||
# And I see that the "Sharing" tab in the details view is eventually loaded
|
||||
# And I set the share with "user1" as not creatable
|
||||
# Then I see that "user1" can not create in the share
|
||||
# And I see that "user1" can not be allowed to create in the share
|
||||
# And I act as Jim
|
||||
# # The Files app is open again to reload the file list
|
||||
# And I open the Files app
|
||||
# And I enter in the folder named "Shared folder"
|
||||
# And I see that it is not possible to create new files
|
||||
|
|
@ -1,811 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @copyright Copyright (c) 2018, Daniel Calviño Sánchez (danxuliu@gmail.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use WebDriver\Key;
|
||||
|
||||
class FilesAppSharingContext implements Context, ActorAwareInterface {
|
||||
use ActorAware;
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function sharedByLabel() {
|
||||
return Locator::forThe()->css(".sharing-entry__reshare")->
|
||||
descendantOf(FilesAppContext::detailsView())->
|
||||
describedAs("Shared by label in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareWithInput() {
|
||||
return Locator::forThe()->css(".sharing-search__input input")->
|
||||
descendantOf(FilesAppContext::detailsView())->
|
||||
describedAs("Share with input in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareWithInputResults() {
|
||||
return Locator::forThe()->css(".vs__dropdown-menu")->
|
||||
describedAs("Share with input results list in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareWithInputResult($result) {
|
||||
return Locator::forThe()->xpath("//li//span[normalize-space() = '$result']/ancestor::li")->
|
||||
descendantOf(self::shareWithInputResults())->
|
||||
describedAs("Share with input result from the results list in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareeList() {
|
||||
return Locator::forThe()->css(".sharing-sharee-list")->
|
||||
descendantOf(FilesAppContext::detailsView())->
|
||||
describedAs("Sharee list in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function sharedWithRow($sharedWithName) {
|
||||
// "username" class is used for any type of share, not only for shares
|
||||
// with users.
|
||||
return Locator::forThe()->xpath("//li[contains(concat(' ', normalize-space(@class), ' '), ' sharing-entry ')]//span[normalize-space() = '$sharedWithName']/ancestor::li")->
|
||||
descendantOf(self::shareeList())->
|
||||
describedAs("Shared with $sharedWithName row in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareWithMenuTrigger($sharedWithName) {
|
||||
return Locator::forThe()->css(".sharing-entry__actions button")->
|
||||
descendantOf(self::sharedWithRow($sharedWithName))->
|
||||
describedAs("Share with $sharedWithName menu trigger in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareWithMenuButton($sharedWithName) {
|
||||
return Locator::forThe()->css(".action-item__menutoggle")->
|
||||
descendantOf(self::shareWithMenuTrigger($sharedWithName))->
|
||||
describedAs("Share with $sharedWithName menu button in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareWithMenu($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return Locator::forThe()->xpath("//*[@id = " . $shareWithMenuTriggerElement->getWrappedElement()->getXpath() . "/@aria-describedby]")->
|
||||
describedAs("Share with $sharedWithName menu in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function permissionCheckboxFor($sharedWithName, $shareWithMenuTriggerElement, $itemText) {
|
||||
// forThe()->checkbox($itemText) can not be used here; that would return
|
||||
// the checkbox itself, but the element that the user interacts with is
|
||||
// the label.
|
||||
return Locator::forThe()->xpath("//label[normalize-space() = '$itemText']")->
|
||||
descendantOf(self::shareWithMenu($sharedWithName, $shareWithMenuTriggerElement))->
|
||||
describedAs("$itemText checkbox in the share with $sharedWithName menu in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function permissionCheckboxInputFor($sharedWithName, $shareWithMenuTriggerElement, $itemText) {
|
||||
return Locator::forThe()->checkbox($itemText)->
|
||||
descendantOf(self::shareWithMenu($sharedWithName, $shareWithMenuTriggerElement))->
|
||||
describedAs("$itemText checkbox input in the share with $sharedWithName menu in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function canEditCheckbox($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return self::permissionCheckboxFor($sharedWithName, $shareWithMenuTriggerElement, 'Allow editing');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function canEditCheckboxInput($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return self::permissionCheckboxInputFor($sharedWithName, $shareWithMenuTriggerElement, 'Allow editing');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function canCreateCheckbox($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return self::permissionCheckboxFor($sharedWithName, $shareWithMenuTriggerElement, 'Allow creating');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function canCreateCheckboxInput($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return self::permissionCheckboxInputFor($sharedWithName, $shareWithMenuTriggerElement, 'Allow creating');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function canReshareCheckbox($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return self::permissionCheckboxFor($sharedWithName, $shareWithMenuTriggerElement, 'Allow resharing');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function canReshareCheckboxInput($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return self::permissionCheckboxInputFor($sharedWithName, $shareWithMenuTriggerElement, 'Allow resharing');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function unshareButton($sharedWithName, $shareWithMenuTriggerElement) {
|
||||
return Locator::forThe()->xpath("//li[contains(concat(' ', normalize-space(@class), ' '), ' action ')]//button[normalize-space() = 'Unshare']")->
|
||||
descendantOf(self::shareWithMenu($sharedWithName, $shareWithMenuTriggerElement))->
|
||||
describedAs("Unshare button in the share with $sharedWithName menu in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareLinkRow() {
|
||||
return Locator::forThe()->css(".sharing-link-list .sharing-entry__link:first-child")->
|
||||
descendantOf(FilesAppContext::detailsView())->
|
||||
describedAs("Share link row in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareLinkAddNewButton() {
|
||||
// When there is no link share the "Add new share" item is shown instead
|
||||
// of the menu button as a direct child of ".share-menu".
|
||||
return Locator::forThe()->css(".action-item.new-share-link")->
|
||||
descendantOf(self::shareLinkRow())->
|
||||
describedAs("Add new share link button in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function copyLinkButton() {
|
||||
return Locator::forThe()->css("a.sharing-entry__copy")->
|
||||
descendantOf(self::shareLinkRow())->
|
||||
describedAs("Copy link button in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareLinkMenuTrigger() {
|
||||
return Locator::forThe()->css(".sharing-entry__actions .action-item__menutoggle")->
|
||||
descendantOf(self::shareLinkRow())->
|
||||
describedAs("Share link menu trigger in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareLinkSingleUnshareAction() {
|
||||
return Locator::forThe()->css(".sharing-entry__actions.icon-close")->
|
||||
descendantOf(self::shareLinkRow())->
|
||||
describedAs("Unshare link single action in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareLinkMenuButton() {
|
||||
return Locator::forThe()->css(".action-item__menutoggle")->
|
||||
descendantOf(self::shareLinkMenuTrigger())->
|
||||
describedAs("Share link menu button in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function shareLinkMenu($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->xpath("//*[@id = " . $shareLinkMenuTriggerElement->getWrappedElement()->getXpath() . "/@aria-describedby]")->
|
||||
describedAs("Share link menu in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function hideDownloadCheckbox($shareLinkMenuTriggerElement) {
|
||||
// forThe()->checkbox("Hide download") can not be used here; that would
|
||||
// return the checkbox itself, but the element that the user interacts
|
||||
// with is the label.
|
||||
return Locator::forThe()->xpath("//label[normalize-space() = 'Hide download']")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Hide download checkbox in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function hideDownloadCheckboxInput($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->checkbox("Hide download")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Hide download checkbox input in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function allowUploadAndEditingRadioButton($shareLinkMenuTriggerElement) {
|
||||
// forThe()->radio("Allow upload and editing") can not be used here;
|
||||
// that would return the radio button itself, but the element that the
|
||||
// user interacts with is the label.
|
||||
return Locator::forThe()->xpath("//label[normalize-space() = 'Allow upload and editing']")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Allow upload and editing radio button in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function passwordProtectCheckbox($shareLinkMenuTriggerElement) {
|
||||
// forThe()->checkbox("Password protect") can not be used here; that
|
||||
// would return the checkbox itself, but the element that the user
|
||||
// interacts with is the label.
|
||||
return Locator::forThe()->xpath("//label[normalize-space() = 'Password protect']")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Password protect checkbox in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function passwordProtectCheckboxInput($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->checkbox("Password protect")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Password protect checkbox input in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function passwordProtectField($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->css(".share-link-password input.input-field__input")->descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Password protect field in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function disabledPasswordProtectField($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->css(".share-link-password input.input-field__input[disabled]")->descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Disabled password protect field in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function passwordProtectByTalkCheckbox($shareLinkMenuTriggerElement) {
|
||||
// forThe()->checkbox("Password protect by Talk") can not be used here;
|
||||
// that would return the checkbox itself, but the element that the user
|
||||
// interacts with is the label.
|
||||
return Locator::forThe()->xpath("//label[normalize-space() = 'Password protect by Talk']")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Password protect by Talk checkbox in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function passwordProtectByTalkCheckboxInput($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->checkbox("Password protect by Talk")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Password protect by Talk checkbox input in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function unshareLinkButton($shareLinkMenuTriggerElement) {
|
||||
return Locator::forThe()->xpath("//li[contains(concat(' ', normalize-space(@class), ' '), ' action ')]//button[normalize-space() = 'Unshare']")->
|
||||
descendantOf(self::shareLinkMenu($shareLinkMenuTriggerElement))->
|
||||
describedAs("Unshare link button in the details view in Files app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I share the link for :fileName
|
||||
*/
|
||||
public function iShareTheLinkFor($fileName) {
|
||||
$this->actor->find(FileListContext::shareActionForFile(FilesAppContext::currentSectionMainView(), $fileName), 10)->click();
|
||||
|
||||
$this->actor->find(self::shareLinkAddNewButton(), 5)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I share :fileName with :shareWithName
|
||||
*/
|
||||
public function iShareWith($fileName, $shareWithName) {
|
||||
$this->actor->find(FileListContext::shareActionForFile(FilesAppContext::currentSectionMainView(), $fileName), 10)->click();
|
||||
|
||||
$this->actor->find(self::shareWithInput(), 5)->setValue($shareWithName);
|
||||
// "setValue()" ends sending a tab, which unfocuses the input and causes
|
||||
// the results to be hidden, so the input needs to be clicked to show
|
||||
// the results again.
|
||||
$this->actor->find(self::shareWithInput())->click();
|
||||
$this->actor->find(self::shareWithInputResult($shareWithName), 5)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I write down the shared link
|
||||
*/
|
||||
public function iWriteDownTheSharedLink() {
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
|
||||
// Close the share link menu if it is open to ensure that it does not
|
||||
// cover the copy link button.
|
||||
if (!WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::shareLinkMenu($shareLinkMenuTriggerElement),
|
||||
$timeout = 2 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
// It may not be possible to click on the menu button (due to the
|
||||
// menu itself covering it), so "Enter" key is pressed instead.
|
||||
$this->actor->find(self::shareLinkMenuButton(), 2)->getWrappedElement()->keyPress(13);
|
||||
}
|
||||
|
||||
$this->actor->find(self::copyLinkButton(), 10)->click();
|
||||
|
||||
// Clicking on the menu item copies the link to the clipboard, but it is
|
||||
// not possible to access that value from the acceptance tests. Due to
|
||||
// this the value of the attribute that holds the URL is used instead.
|
||||
$this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::copyLinkButton(), 2)->getWrappedElement()->getAttribute("href");
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the download of the shared link as hidden
|
||||
*/
|
||||
public function iSetTheDownloadOfTheSharedLinkAsHidden() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$this->iSeeThatTheDownloadOfTheLinkShareIsShown();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::hideDownloadCheckbox($shareLinkMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the download of the shared link as shown
|
||||
*/
|
||||
public function iSetTheDownloadOfTheSharedLinkAsShown() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$this->iSeeThatTheDownloadOfTheLinkShareIsHidden();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::hideDownloadCheckbox($shareLinkMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the shared link as editable
|
||||
*/
|
||||
public function iSetTheSharedLinkAsEditable() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::allowUploadAndEditingRadioButton($shareLinkMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I protect the shared link with the password :password
|
||||
*/
|
||||
public function iProtectTheSharedLinkWithThePassword($password) {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::passwordProtectCheckbox($shareLinkMenuTriggerElement), 2)->click();
|
||||
|
||||
$this->actor->find(self::passwordProtectField($shareLinkMenuTriggerElement), 2)->setValue($password . Key::ENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the password of the shared link as protected by Talk
|
||||
*/
|
||||
public function iSetThePasswordOfTheSharedLinkAsProtectedByTalk() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$this->iSeeThatThePasswordOfTheLinkShareIsNotProtectedByTalk();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::passwordProtectByTalkCheckbox($shareLinkMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the password of the shared link as not protected by Talk
|
||||
*/
|
||||
public function iSetThePasswordOfTheSharedLinkAsNotProtectedByTalk() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$this->iSeeThatThePasswordOfTheLinkShareIsProtectedByTalk();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::passwordProtectByTalkCheckbox($shareLinkMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the share with :shareWithName as not editable
|
||||
*/
|
||||
public function iSetTheShareWithAsNotEditable($shareWithName) {
|
||||
$this->showShareWithMenuIfNeeded($shareWithName);
|
||||
|
||||
$this->iSeeThatCanEditTheShare($shareWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($shareWithName), 2);
|
||||
$this->actor->find(self::canEditCheckbox($shareWithName, $shareWithMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the share with :shareWithName as not creatable
|
||||
*/
|
||||
public function iSetTheShareWithAsNotCreatable($shareWithName) {
|
||||
$this->showShareWithMenuIfNeeded($shareWithName);
|
||||
|
||||
$this->iSeeThatCanCreateInTheShare($shareWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($shareWithName), 2);
|
||||
$this->actor->find(self::canCreateCheckbox($shareWithName, $shareWithMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I set the share with :shareWithName as not reshareable
|
||||
*/
|
||||
public function iSetTheShareWithAsNotReshareable($shareWithName) {
|
||||
$this->showShareWithMenuIfNeeded($shareWithName);
|
||||
|
||||
$this->iSeeThatCanReshareTheShare($shareWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($shareWithName), 2);
|
||||
$this->actor->find(self::canReshareCheckbox($shareWithName, $shareWithMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I unshare the share with :shareWithName
|
||||
*/
|
||||
public function iUnshareTheFileWith($shareWithName) {
|
||||
$this->showShareWithMenuIfNeeded($shareWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($shareWithName), 2);
|
||||
$this->actor->find(self::unshareButton($shareWithName, $shareWithMenuTriggerElement), 2)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I unshare the link share
|
||||
*/
|
||||
public function iUnshareTheLink() {
|
||||
try {
|
||||
$this->actor->find(self::shareLinkSingleUnshareAction(), 2)->click();
|
||||
} catch (NoSuchElementException $e) {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
$this->actor->find(self::unshareLinkButton($shareLinkMenuTriggerElement), 2)->click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the file is shared with me by :sharedByName
|
||||
*/
|
||||
public function iSeeThatTheFileIsSharedWithMeBy($sharedByName) {
|
||||
Assert::assertEquals(
|
||||
$this->actor->find(self::sharedByLabel(), 10)->getText(), "Shared with you by $sharedByName");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the file is shared with :sharedWithName
|
||||
*/
|
||||
public function iSeeThatTheFileIsSharedWith($sharedWithName) {
|
||||
Assert::assertTrue(
|
||||
$this->actor->find(self::sharedWithRow($sharedWithName), 10)->isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the file is not shared with :sharedWithName
|
||||
*/
|
||||
public function iSeeThatTheFileIsNotSharedWith($sharedWithName) {
|
||||
if (!WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::sharedWithRow($sharedWithName),
|
||||
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
Assert::fail("The shared with $sharedWithName row is still shown after $timeout seconds");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that resharing the file is not allowed
|
||||
*/
|
||||
public function iSeeThatResharingTheFileIsNotAllowed() {
|
||||
Assert::assertEquals(
|
||||
$this->actor->find(self::shareWithInput(), 10)->getWrappedElement()->getAttribute("disabled"), "disabled");
|
||||
Assert::assertEquals(
|
||||
$this->actor->find(self::shareWithInput(), 10)->getWrappedElement()->getAttribute("placeholder"), "Resharing is not allowed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that resharing the file by link is not available
|
||||
*/
|
||||
public function iSeeThatResharingTheFileByLinkIsNotAvailable() {
|
||||
if (!WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::shareLinkAddNewButton(),
|
||||
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
Assert::fail("The add new share link button is still shown after $timeout seconds");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can not be allowed to edit the share
|
||||
*/
|
||||
public function iSeeThatCanNotBeAllowedToEditTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertEquals(
|
||||
$this->actor->find(self::canEditCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->getWrappedElement()->getAttribute("disabled"), "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can edit the share
|
||||
*/
|
||||
public function iSeeThatCanEditTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertTrue(
|
||||
$this->actor->find(self::canEditCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can not edit the share
|
||||
*/
|
||||
public function iSeeThatCanNotEditTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertFalse(
|
||||
$this->actor->find(self::canEditCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can not be allowed to create in the share
|
||||
*/
|
||||
public function iSeeThatCanNotBeAllowedToCreateInTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertEquals(
|
||||
$this->actor->find(self::canCreateCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->getWrappedElement()->getAttribute("disabled"), "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can create in the share
|
||||
*/
|
||||
public function iSeeThatCanCreateInTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertTrue(
|
||||
$this->actor->find(self::canCreateCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can not create in the share
|
||||
*/
|
||||
public function iSeeThatCanNotCreateInTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertFalse(
|
||||
$this->actor->find(self::canCreateCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that resharing for :sharedWithName is not available
|
||||
*/
|
||||
public function iSeeThatResharingForIsNotAvailable($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
if (!WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::canReshareCheckbox($sharedWithName, $shareWithMenuTriggerElement),
|
||||
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
Assert::fail("The resharing checkbox for $sharedWithName is still shown after $timeout seconds");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can reshare the share
|
||||
*/
|
||||
public function iSeeThatCanReshareTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertTrue(
|
||||
$this->actor->find(self::canReshareCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that :sharedWithName can not reshare the share
|
||||
*/
|
||||
public function iSeeThatCanNotReshareTheShare($sharedWithName) {
|
||||
$this->showShareWithMenuIfNeeded($sharedWithName);
|
||||
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($sharedWithName), 10);
|
||||
Assert::assertFalse(
|
||||
$this->actor->find(self::canReshareCheckboxInput($sharedWithName, $shareWithMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the download of the link share is hidden
|
||||
*/
|
||||
public function iSeeThatTheDownloadOfTheLinkShareIsHidden() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
Assert::assertTrue($this->actor->find(self::hideDownloadCheckboxInput($shareLinkMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the download of the link share is shown
|
||||
*/
|
||||
public function iSeeThatTheDownloadOfTheLinkShareIsShown() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
Assert::assertFalse($this->actor->find(self::hideDownloadCheckboxInput($shareLinkMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the password protect is disabled while loading
|
||||
*/
|
||||
public function iSeeThatThePasswordProtectIsDisabledWhileLoading() {
|
||||
// Due to the additional time needed to find the menu trigger element it
|
||||
// could happen that the request to modify the password protect was
|
||||
// completed and the field enabled again even before finding the
|
||||
// disabled field started. Therefore, if the disabled field could not be
|
||||
// found it is just assumed that it was already enabled again.
|
||||
// Nevertheless, this check should be done anyway to ensure that the
|
||||
// following scenario steps are not executed before the request to the
|
||||
// server was done.
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
|
||||
try {
|
||||
$this->actor->find(self::disabledPasswordProtectField($shareLinkMenuTriggerElement), 5);
|
||||
} catch (NoSuchElementException $exception) {
|
||||
echo "The password protect field was not found disabled after " . (5 * $this->actor->getFindTimeoutMultiplier()) . " seconds, assumming that it was disabled and enabled again before the check started and continuing";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::disabledPasswordProtectField($shareLinkMenuTriggerElement),
|
||||
$timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
Assert::fail("The password protect field is still disabled after $timeout seconds");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the link share is password protected
|
||||
*/
|
||||
public function iSeeThatTheLinkShareIsPasswordProtected() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
Assert::assertTrue($this->actor->find(self::passwordProtectCheckboxInput($shareLinkMenuTriggerElement), 10)->isChecked(), "Password protect checkbox is checked");
|
||||
Assert::assertTrue($this->actor->find(self::passwordProtectField($shareLinkMenuTriggerElement), 10)->isVisible(), "Password protect field is visible");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the password of the link share is protected by Talk
|
||||
*/
|
||||
public function iSeeThatThePasswordOfTheLinkShareIsProtectedByTalk() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
Assert::assertTrue($this->actor->find(self::passwordProtectByTalkCheckboxInput($shareLinkMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the password of the link share is not protected by Talk
|
||||
*/
|
||||
public function iSeeThatThePasswordOfTheLinkShareIsNotProtectedByTalk() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
Assert::assertFalse($this->actor->find(self::passwordProtectByTalkCheckboxInput($shareLinkMenuTriggerElement), 10)->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the checkbox to protect the password of the link share by Talk is not shown
|
||||
*/
|
||||
public function iSeeThatTheCheckboxToProtectThePasswordOfTheLinkShareByTalkIsNotShown() {
|
||||
$this->showShareLinkMenuIfNeeded();
|
||||
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 10);
|
||||
try {
|
||||
Assert::assertFalse(
|
||||
$this->actor->find(self::passwordProtectByTalkCheckbox($shareLinkMenuTriggerElement))->isVisible());
|
||||
} catch (NoSuchElementException $exception) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given I share the link for :fileName protected by the password :password
|
||||
*/
|
||||
public function iShareTheLinkForProtectedByThePassword($fileName, $password) {
|
||||
$this->iShareTheLinkFor($fileName);
|
||||
$this->iProtectTheSharedLinkWithThePassword($password);
|
||||
$this->iSeeThatThePasswordProtectIsDisabledWhileLoading();
|
||||
}
|
||||
|
||||
private function showShareLinkMenuIfNeeded() {
|
||||
$shareLinkMenuTriggerElement = $this->actor->find(self::shareLinkMenuTrigger(), 2);
|
||||
|
||||
// In some cases the share menu is hidden after clicking on an action of
|
||||
// the menu. Therefore, if the menu is visible, wait a little just in
|
||||
// case it is in the process of being hidden due to a previous action,
|
||||
// in which case it is shown again.
|
||||
if (WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::shareLinkMenu($shareLinkMenuTriggerElement),
|
||||
$timeout = 2 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
$this->actor->find(self::shareLinkMenuButton(), 10)->click();
|
||||
}
|
||||
}
|
||||
|
||||
private function showShareWithMenuIfNeeded($shareWithName) {
|
||||
$shareWithMenuTriggerElement = $this->actor->find(self::shareWithMenuTrigger($shareWithName), 2);
|
||||
|
||||
// In some cases the share menu is hidden after clicking on an action of
|
||||
// the menu. Therefore, if the menu is visible, wait a little just in
|
||||
// case it is in the process of being hidden due to a previous action,
|
||||
// in which case it is shown again.
|
||||
if (WaitFor::elementToBeEventuallyNotShown(
|
||||
$this->actor,
|
||||
self::shareWithMenu($shareWithName, $shareWithMenuTriggerElement),
|
||||
$timeout = 2 * $this->actor->getFindTimeoutMultiplier())) {
|
||||
$this->actor->find(self::shareWithMenuButton($shareWithName), 10)->click();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue