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:
fenn-cs 2023-07-19 02:11:27 +01:00 committed by Louis Chemineau
parent db1a7ba03a
commit 1d74d62d2a
25 changed files with 1474 additions and 2170 deletions

View file

@ -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

View file

@ -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>

View file

@ -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);

View file

@ -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>

View file

@ -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">

View file

@ -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,

View file

@ -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,
}
/**

View 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)
},
},
}

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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 },
)
}
}

File diff suppressed because it is too large Load diff

View file

@ -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: {

View file

@ -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>

View file

@ -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

File diff suppressed because one or more lines are too long

View file

@ -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 */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -17,7 +17,6 @@ default:
- FileListContext
- FilePickerContext
- FilesAppContext
- FilesAppSharingContext
- LoginPageContext
- NotificationsContext
- PublicShareContext
@ -47,7 +46,6 @@ default:
- FileListContext
- FilePickerContext
- FilesAppContext
- FilesAppSharingContext
- LoginPageContext
- NotificationsContext
- PublicShareContext

View file

@ -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!"

View file

@ -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

View file

@ -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();
}
}
}