Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(files_sharing): Add missing "note to recipient" #46892

Merged
merged 4 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions apps/files_sharing/src/files_headers/noteToRecipient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ComponentPublicInstance, VueConstructor } from 'vue'

import { Folder, Header, registerFileListHeaders } from '@nextcloud/files'
import Vue from 'vue'

type IFilesHeaderNoteToRecipient = ComponentPublicInstance & { updateFolder: (folder: Folder) => void }

/**
* Register the "note to recipient" as a files list header
*/
export default function registerNoteToRecipient() {
let FilesHeaderNoteToRecipient: VueConstructor
let instance: IFilesHeaderNoteToRecipient

registerFileListHeaders(new Header({
id: 'note-to-recipient',
order: 0,
// Always if there is a note
enabled: (folder: Folder) => Boolean(folder.attributes.note),
// Update the root folder if needed
updated: (folder: Folder) => {
instance.updateFolder(folder)
},
// render simply spawns the component
render: async (el: HTMLElement, folder: Folder) => {
if (FilesHeaderNoteToRecipient === undefined) {
const { default: component } = await import('../views/FilesHeaderNoteToRecipient.vue')
FilesHeaderNoteToRecipient = Vue.extend(component)
}
instance = new FilesHeaderNoteToRecipient().$mount(el) as unknown as IFilesHeaderNoteToRecipient
instance.updateFolder(folder)
},
}))
}
10 changes: 8 additions & 2 deletions apps/files_sharing/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { addNewFileMenuEntry, registerDavProperty } from '@nextcloud/files'
import { registerAccountFilter } from './filters/AccountFilter'
import { entry as newFileRequest } from './new/newFileRequest.ts'

import registerNoteToRecipient from './files_headers/noteToRecipient'
import registerSharingViews from './views/shares'

import { entry as newFileRequest } from './new/newFileRequest'
import './actions/acceptShareAction'
import './actions/openInFilesAction'
import './actions/rejectShareAction'
import './actions/restoreShareAction'
import './actions/sharingStatusAction'
import { registerAccountFilter } from './filters/AccountFilter'

registerSharingViews()

addNewFileMenuEntry(newFileRequest)

registerDavProperty('nc:note', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:sharees', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:share-attributes', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('oc:share-types', { oc: 'http://owncloud.org/ns' })
registerDavProperty('ocs:share-permissions', { ocs: 'http://open-collaboration-services.org/ns' })

registerAccountFilter()

// Add "note to recipient" message
registerNoteToRecipient()
73 changes: 73 additions & 0 deletions apps/files_sharing/src/views/FilesHeaderNoteToRecipient.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcNoteCard v-if="note.length > 0"
class="note-to-recipient"
type="info">
<p v-if="user" class="note-to-recipient__heading">
{{ t('files_sharing', 'Note from') }}
<NcUserBubble :user="user.id" :display-name="user.displayName" />
</p>
<p v-else class="note-to-recipient__heading">
{{ t('files_sharing', 'Note:') }}
</p>
<p class="note-to-recipient__text" v-text="note" />
</NcNoteCard>
</template>

<script setup lang="ts">
import type { Folder } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { t } from '@nextcloud/l10n'
import { computed, ref } from 'vue'

import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcUserBubble from '@nextcloud/vue/dist/Components/NcUserBubble.js'

const folder = ref<Folder>()
const note = computed<string>(() => folder.value?.attributes.note ?? '')
const user = computed(() => {
const id = folder.value?.attributes?.['owner-id']
const displayName = folder.value?.attributes?.['owner-display-name']
if (id !== getCurrentUser()?.uid) {
return {
id,
displayName,
}
}
return null
})

/**
* Update the current folder
* @param newFolder the new folder to show note for
*/
function updateFolder(newFolder: Folder) {
folder.value = newFolder
}

defineExpose({ updateFolder })
</script>

<style scoped>
.note-to-recipient {
margin-inline: var(--row-height)
}

.note-to-recipient__text {
/* respect new lines */
white-space: pre-line;
}

.note-to-recipient__heading {
font-weight: bold;
}

@media screen and (max-width: 512px) {
.note-to-recipient {
margin-inline: var(--default-grid-baseline);
}
}
</style>
32 changes: 19 additions & 13 deletions apps/files_sharing/src/views/SharingDetailsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,9 @@
{{ t('files_sharing', 'Note to recipient') }}
</NcCheckboxRadioSwitch>
<template v-if="writeNoteToRecipientIsChecked">
<label for="share-note-textarea">
{{ t('files_sharing', 'Enter a note for the share recipient') }}
</label>
<textarea id="share-note-textarea" :value="share.note" @input="share.note = $event.target.value" />
<NcTextArea :label="t('files_sharing', 'Note to recipient')"
:placeholder="t('files_sharing', 'Enter a note for the share recipient')"
:value.sync="share.note" />
</template>
<ExternalShareAction v-for="action in externalLinkActions"
:id="action.id"
Expand Down Expand Up @@ -247,15 +246,17 @@
<script>
import { emit } from '@nextcloud/event-bus'
import { getLanguage } from '@nextcloud/l10n'
import { Type as ShareType } from '@nextcloud/sharing'
import { ShareType } from '@nextcloud/sharing'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js'

import CircleIcon from 'vue-material-design-icons/CircleOutline.vue'
import CloseIcon from 'vue-material-design-icons/Close.vue'
import EditIcon from 'vue-material-design-icons/Pencil.vue'
Expand Down Expand Up @@ -290,11 +291,12 @@ export default {
components: {
NcAvatar,
NcButton,
NcInputField,
NcPasswordField,
NcDateTimePickerNative,
NcCheckboxRadioSwitch,
NcDateTimePickerNative,
NcInputField,
NcLoadingIcon,
NcPasswordField,
NcTextArea,
CloseIcon,
CircleIcon,
EditIcon,
Expand Down Expand Up @@ -717,7 +719,7 @@ export default {
* @return {Array}
*/
externalLinkActions() {
const filterValidAction = (action) => (action.shareType.includes(ShareType.SHARE_TYPE_LINK) || action.shareType.includes(ShareType.SHARE_TYPE_EMAIL)) && action.advanced
const filterValidAction = (action) => (action.shareType.includes(ShareType.Link) || action.shareType.includes(ShareType.Email)) && action.advanced
// filter only the advanced registered actions for said link
return this.ExternalShareActions.actions
.filter(filterValidAction)
Expand Down Expand Up @@ -808,6 +810,10 @@ export default {
this.advancedSectionAccordionExpanded = true
}

if (this.share.note) {
this.writeNoteToRecipientIsChecked = true
}

},
handleShareType() {
if ('shareType' in this.share) {
Expand Down
8 changes: 6 additions & 2 deletions cypress/e2e/files_sharing/FilesSharingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
/* eslint-disable jsdoc/require-jsdoc */
import { triggerActionForFile } from '../files/FilesUtils'

import { EntryId as FileRequestEntryID } from '../../../apps/files_sharing/src/new/newFileRequest'

export interface ShareSetting {
read: boolean
update: boolean
delete: boolean
share: boolean
download: boolean
note: string
}

export function createShare(fileName: string, username: string, shareSettings: Partial<ShareSetting> = {}) {
Expand Down Expand Up @@ -85,6 +84,11 @@ export function updateShare(fileName: string, index: number, shareSettings: Part
}
}

if (shareSettings.note !== undefined) {
cy.findByRole('checkbox', { name: /note to recipient/i }).check({ force: true, scrollBehavior: 'nearest' })
cy.findByRole('textbox', { name: /note to recipient/i }).type(shareSettings.note)
}

cy.get('[data-cy-files-sharing-share-editor-action="save"]').click({ scrollBehavior: 'nearest' })

cy.wait('@updateShare')
Expand Down
73 changes: 73 additions & 0 deletions cypress/e2e/files_sharing/note-to-recipient.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { User } from '@nextcloud/cypress'
import { createShare, openSharingPanel } from './FilesSharingUtils.ts'
import { getRowForFile, navigateToFolder } from '../files/FilesUtils.ts'

describe('files_sharing: Note to recipient', { testIsolation: true }, () => {
let user: User
let sharee: User

beforeEach(() => {
cy.createRandomUser().then(($user) => {
user = $user
})
cy.createRandomUser().then(($user) => {
sharee = $user
})
})

it('displays the note to the sharee', () => {
cy.mkdir(user, '/folder')
cy.uploadContent(user, new Blob([]), 'text/plain', '/folder/file')
cy.login(user)
cy.visit('/apps/files')

// share the folder
createShare('folder', sharee.userId, { read: true, download: true, note: 'Hello, this is the note.' })

cy.logout()
// Now for the sharee
cy.login(sharee)

// visit shared files view
cy.visit('/apps/files')
navigateToFolder('folder')
cy.get('.note-to-recipient')
.should('be.visible')
.and('contain.text', 'Hello, this is the note.')
})

/**
* Regression test for https://github.com/nextcloud/server/issues/46188
*/
it('shows an existing note when editing a share', () => {
cy.mkdir(user, '/folder')
cy.login(user)
cy.visit('/apps/files')

// share the folder
createShare('folder', sharee.userId, { read: true, download: true, note: 'Hello, this is the note.' })

// reload just to be sure
cy.reload()

// open the sharing tab
openSharingPanel('folder')

cy.get('[data-cy-sidebar]').within(() => {
// Open the share
cy.get('[data-cy-files-sharing-share-actions]').first().click()
// Open the custom settings
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click()

cy.findByRole('checkbox', { name: /note to recipient/i })
.and('be.checked')
cy.findByRole('textbox', { name: /note to recipient/i })
.should('be.visible')
.and('have.value', 'Hello, this is the note.')
})
})
})
2 changes: 2 additions & 0 deletions dist/2351-2351.js

Large diffs are not rendered by default.

File renamed without changes.
1 change: 1 addition & 0 deletions dist/2351-2351.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/2351-2351.js.map.license
2 changes: 2 additions & 0 deletions dist/5804-5804.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading