From 365b647b60d9380bbbd88fb00984b294db9062c2 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 16 Jul 2024 17:59:03 +0200 Subject: fix(files_sharing): file request conditions with link/email global settings Signed-off-by: skjnldsv --- .../src/components/NewFileRequestDialog.vue | 24 ++++++++++++++++------ apps/files_sharing/src/new/newFileRequest.ts | 17 +++++++++------ apps/files_sharing/src/services/ConfigService.ts | 9 +++++++- 3 files changed, 37 insertions(+), 13 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue index 35cd4395290..2e15babeae5 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -95,7 +95,7 @@ @click="onFinish"> {{ finishButtonLabel }} @@ -182,6 +182,7 @@ export default defineComponent({ return { currentStep: STEP.FIRST, loading: false, + success: false, destination: this.context.path || '/', label: '', @@ -244,10 +245,19 @@ export default defineComponent({ return } - await this.setShareEmails() - await this.sendEmails() - showSuccess(this.t('files_sharing', 'File request created and emails sent')) - this.$emit('close') + if (sharingConfig.isMailShareAllowed && this.emails.length > 0) { + await this.setShareEmails() + await this.sendEmails() + showSuccess(this.n('files_sharing', 'File request created and email sent', 'File request created and {count} emails sent', this.emails.length, { count: this.emails.length })) + } else { + showSuccess(this.t('files_sharing', 'File request created')) + } + + // Show success then close + this.success = true + setTimeout(() => { + this.$emit('close') + }, 3000) }, async createShare() { @@ -258,7 +268,9 @@ export default defineComponent({ const shareUrl = generateOcsUrl('apps/files_sharing/api/v1/shares') try { const request = await axios.post(shareUrl, { - shareType: ShareType.Email, + // Always create a file request, but without mail share + // permissions, only a share link will be created. + shareType: sharingConfig.isMailShareAllowed ? ShareType.Email : ShareType.Link, permissions: Permission.CREATE, label: this.label, diff --git a/apps/files_sharing/src/new/newFileRequest.ts b/apps/files_sharing/src/new/newFileRequest.ts index b7e5b3f2144..c5a0ca07bd1 100644 --- a/apps/files_sharing/src/new/newFileRequest.ts +++ b/apps/files_sharing/src/new/newFileRequest.ts @@ -4,22 +4,27 @@ */ import type { Entry, Folder, Node } from '@nextcloud/files' +import { Permission } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' -import Vue, { defineAsyncComponent } from 'vue' import FileUploadSvg from '@mdi/svg/svg/file-upload.svg?raw' +import Vue, { defineAsyncComponent } from 'vue' +import Config from '../services/ConfigService' const NewFileRequestDialogVue = defineAsyncComponent(() => import('../components/NewFileRequestDialog.vue')) +const sharingConfig = new Config() + export const entry = { id: 'file-request', displayName: t('files', 'Create new file request'), iconSvgInline: FileUploadSvg, order: 30, - enabled(): boolean { - // TODO: determine requirements - // 1. user can share the root folder - // 2. OR user can create subfolders ? - return true + enabled(context: Folder): boolean { + if ((context.permissions & Permission.SHARE) !== 0) { + // We need to have either link shares creation permissions + return sharingConfig.isPublicShareAllowed + } + return false }, async handler(context: Folder, content: Node[]) { // Create document root diff --git a/apps/files_sharing/src/services/ConfigService.ts b/apps/files_sharing/src/services/ConfigService.ts index f8f7994bdab..94db0454428 100644 --- a/apps/files_sharing/src/services/ConfigService.ts +++ b/apps/files_sharing/src/services/ConfigService.ts @@ -210,6 +210,13 @@ export default class Config { return window.OC.appConfig.core.remoteShareAllowed === true } + /** + * Is public sharing enabled ? + */ + get isPublicShareAllowed(): boolean { + return this._capabilities?.files_sharing?.public?.enabled === true + } + /** * Is sharing my mail (link share) enabled ? */ @@ -217,7 +224,7 @@ export default class Config { // eslint-disable-next-line camelcase return this._capabilities?.files_sharing?.sharebymail?.enabled === true // eslint-disable-next-line camelcase - && this._capabilities?.files_sharing?.public?.enabled === true + && this.isPublicShareAllowed === true } /** -- cgit v1.2.3 From 018e4c001de391484d7c0433cc092b0fbd8866c2 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 16 Jul 2024 18:01:55 +0200 Subject: fix(files_sharing): file request use l10n `t` and `n` aliases Signed-off-by: skjnldsv --- .../src/components/NewFileRequestDialog.vue | 27 +++++++++++----------- .../NewFileRequestDialogDatePassword.vue | 10 ++++---- .../NewFileRequestDialogFinish.vue | 19 ++++++++------- .../NewFileRequestDialogIntro.vue | 8 +++---- 4 files changed, 31 insertions(+), 33 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue index 2e15babeae5..3bca39d4f38 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -115,7 +115,7 @@ import { generateOcsUrl } from '@nextcloud/router' import { Permission } from '@nextcloud/files' import { ShareType } from '@nextcloud/sharing' import { showError, showSuccess } from '@nextcloud/dialogs' -import { translate, translatePlural } from '@nextcloud/l10n' +import { n, t } from '@nextcloud/l10n' import axios from '@nextcloud/axios' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' @@ -170,9 +170,8 @@ export default defineComponent({ setup() { return { STEP, - - n: translatePlural, - t: translate, + n, + t, isShareByMailEnabled: sharingConfig.isMailShareAllowed, } @@ -199,9 +198,9 @@ export default defineComponent({ computed: { finishButtonLabel() { if (this.emails.length === 0) { - return this.t('files_sharing', 'Close') + return t('files_sharing', 'Close') } - return this.n('files_sharing', 'Close and send email', 'Close and send {count} emails', this.emails.length, { count: this.emails.length }) + return n('files_sharing', 'Close and send email', 'Close and send {count} emails', this.emails.length, { count: this.emails.length }) }, }, @@ -216,7 +215,7 @@ export default defineComponent({ // cannot share root if (this.destination === '/' || this.destination === '') { const destinationInput = form.querySelector('input[name="destination"]') as HTMLInputElement - destinationInput?.setCustomValidity(this.t('files_sharing', 'Please select a folder, you cannot share the root directory.')) + destinationInput?.setCustomValidity(t('files_sharing', 'Please select a folder, you cannot share the root directory.')) form.reportValidity() return } @@ -240,7 +239,7 @@ export default defineComponent({ async onFinish() { if (this.emails.length === 0 || this.isShareByMailEnabled === false) { - showSuccess(this.t('files_sharing', 'File request created')) + showSuccess(t('files_sharing', 'File request created')) this.$emit('close') return } @@ -248,9 +247,9 @@ export default defineComponent({ if (sharingConfig.isMailShareAllowed && this.emails.length > 0) { await this.setShareEmails() await this.sendEmails() - showSuccess(this.n('files_sharing', 'File request created and email sent', 'File request created and {count} emails sent', this.emails.length, { count: this.emails.length })) + showSuccess(n('files_sharing', 'File request created and email sent', 'File request created and {count} emails sent', this.emails.length, { count: this.emails.length })) } else { - showSuccess(this.t('files_sharing', 'File request created')) + showSuccess(t('files_sharing', 'File request created')) } // Show success then close @@ -306,8 +305,8 @@ export default defineComponent({ const errorMessage = (error as AxiosError)?.response?.data?.ocs?.meta?.message showError( errorMessage - ? this.t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) - : this.t('files_sharing', 'Error creating the share'), + ? t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) + : t('files_sharing', 'Error creating the share'), ) logger.error('Error while creating share', { error, errorMessage }) throw error @@ -378,8 +377,8 @@ export default defineComponent({ const errorMessage = error.response?.data?.ocs?.meta?.message showError( errorMessage - ? this.t('files_sharing', 'Error sending emails: {errorMessage}', { errorMessage }) - : this.t('files_sharing', 'Error sending emails'), + ? t('files_sharing', 'Error sending emails: {errorMessage}', { errorMessage }) + : t('files_sharing', 'Error sending emails'), ) logger.error('Error while sending emails', { error, errorMessage }) }, diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue index 9bb1863e1d1..e88496c8416 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue @@ -81,7 +81,7 @@ + diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 109eaf2e9da..7620d309ff6 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -27,6 +27,7 @@ + get(\bantu\IniGetWrapper\IniGetWrapper::class)->getBytes('upload_max_filesize'); $post_max_size = OC::$server->get(\bantu\IniGetWrapper\IniGetWrapper::class)->getBytes('post_max_size'); @@ -102,14 +103,11 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size); class="emptycontent has-note">
-

t('Upload files to %s', [$_['shareOwner']])) ?>

-

- -
-

t('Upload files to %s', [$_['label']])) ?>

- +

t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?>

+

t('%s shared a folder with you.', [$_['shareOwner']])) ?>

+
-

t('Upload files to %s', [$_['filename']])) ?>

+

t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?>

diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php index 493ac10a24b..79b90d8a156 100644 --- a/apps/files_sharing/tests/Controller/ShareControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php @@ -22,6 +22,7 @@ use OCP\AppFramework\Http\Template\ExternalShareMenuAction; use OCP\AppFramework\Http\Template\LinkMenuAction; use OCP\AppFramework\Http\Template\PublicTemplateResponse; use OCP\AppFramework\Http\Template\SimpleMenuAction; +use OCP\AppFramework\Services\IInitialState; use OCP\Constants; use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; @@ -121,6 +122,7 @@ class ShareControllerTest extends \Test\TestCase { $this->defaults, $this->config, $this->createMock(IRequest::class), + $this->createMock(IInitialState::class) ) ); @@ -350,7 +352,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => 'downloadURL', 'note' => $note, 'hideDownload' => false, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -511,7 +514,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => 'downloadURL', 'note' => $note, 'hideDownload' => false, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -672,7 +676,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => 'downloadURL', 'note' => $note, 'hideDownload' => true, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -798,7 +803,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => '', 'note' => '', 'hideDownload' => false, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); diff --git a/webpack.modules.js b/webpack.modules.js index d13ad284bab..887d8dc75a0 100644 --- a/webpack.modules.js +++ b/webpack.modules.js @@ -54,6 +54,7 @@ module.exports = { init: path.join(__dirname, 'apps/files_sharing/src', 'init.ts'), main: path.join(__dirname, 'apps/files_sharing/src', 'main.ts'), 'personal-settings': path.join(__dirname, 'apps/files_sharing/src', 'personal-settings.js'), + 'public-file-request': path.join(__dirname, 'apps/files_sharing/src', 'public-file-request.ts'), }, files_trashbin: { init: path.join(__dirname, 'apps/files_trashbin/src', 'files-init.ts'), -- cgit v1.2.3 From 2c9440496958874ff79db2e1d1b11f6a29f45a73 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Thu, 18 Jul 2024 08:27:42 +0200 Subject: fix(files_sharing): also allow removing READ permissions on email shares Signed-off-by: skjnldsv --- apps/files_sharing/src/views/SharingDetailsTab.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index 023c137a254..a0cb71fc392 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -603,7 +603,10 @@ export default { return (this.fileInfo.canDownload() || this.canDownload) }, canRemoveReadPermission() { - return this.allowsFileDrop && this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK + return this.allowsFileDrop && ( + this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK + || this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL + ) }, // if newPassword exists, but is empty, it means // the user deleted the original password -- cgit v1.2.3 From 320af319f9ec715a46e0c8f03007448413dd2568 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Thu, 18 Jul 2024 09:27:13 +0200 Subject: fix(files_sharing): improve file request info messages Signed-off-by: skjnldsv --- .../files_sharing/src/actions/openInFilesAction.ts | 2 +- .../src/components/NewFileRequestDialog.vue | 22 +++++++++++++----- .../NewFileRequestDialogDatePassword.vue | 26 +++++++++++++++------- .../NewFileRequestDialogIntro.vue | 15 ++++++++++++- .../src/components/SelectShareFolderDialogue.vue | 6 ++--- apps/files_sharing/src/new/newFileRequest.ts | 2 +- 6 files changed, 54 insertions(+), 19 deletions(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/src/actions/openInFilesAction.ts b/apps/files_sharing/src/actions/openInFilesAction.ts index 51b9ec84a1d..82b66927c9e 100644 --- a/apps/files_sharing/src/actions/openInFilesAction.ts +++ b/apps/files_sharing/src/actions/openInFilesAction.ts @@ -11,7 +11,7 @@ import { sharesViewId, sharedWithYouViewId, sharedWithOthersViewId, sharingByLin export const action = new FileAction({ id: 'open-in-files', - displayName: () => t('files', 'Open in Files'), + displayName: () => t('files_sharing', 'Open in Files'), iconSvgInline: () => '', enabled: (nodes, view) => [ diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue index 4c476af9bc8..b943169963a 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -398,7 +398,7 @@ export default defineComponent({ }) - diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue index 231ed94c460..805b13fdf95 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue @@ -26,7 +26,6 @@ + +

+ + {{ t('files_sharing', 'The uploaded files are visible only to you unless you choose to share them.') }} +

@@ -56,6 +60,11 @@ :required="false" name="note" @update:value="$emit('update:note', $event)" /> + +

+ + {{ t('files_sharing', 'You can add links, date or any other information that will help the recipient understand what you are requesting.') }} +

@@ -69,6 +78,8 @@ import { getFilePickerBuilder } from '@nextcloud/dialogs' import { t } from '@nextcloud/l10n' import IconFolder from 'vue-material-design-icons/Folder.vue' +import IconInfo from 'vue-material-design-icons/Information.vue' +import IconLock from 'vue-material-design-icons/Lock.vue' import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' @@ -77,6 +88,8 @@ export default defineComponent({ components: { IconFolder, + IconInfo, + IconLock, NcTextArea, NcTextField, }, diff --git a/apps/files_sharing/src/components/SelectShareFolderDialogue.vue b/apps/files_sharing/src/components/SelectShareFolderDialogue.vue index 1b717da8b67..ec29aff9b8a 100644 --- a/apps/files_sharing/src/components/SelectShareFolderDialogue.vue +++ b/apps/files_sharing/src/components/SelectShareFolderDialogue.vue @@ -57,7 +57,7 @@ export default { async pickFolder() { // Setup file picker - const picker = getFilePickerBuilder(t('files', 'Choose a default folder for accepted shares')) + const picker = getFilePickerBuilder(t('files_sharing', 'Choose a default folder for accepted shares')) .startAt(this.readableDirectory) .setMultiSelect(false) .setType(1) @@ -69,7 +69,7 @@ export default { // Init user folder picking const dir = await picker.pick() || '/' if (!dir.startsWith('/')) { - throw new Error(t('files', 'Invalid path selected')) + throw new Error(t('files_sharing', 'Invalid path selected')) } // Fix potential path issues and save results @@ -78,7 +78,7 @@ export default { shareFolder: this.directory, }) } catch (error) { - showError(error.message || t('files', 'Unknown error')) + showError(error.message || t('files_sharing', 'Unknown error')) } }, diff --git a/apps/files_sharing/src/new/newFileRequest.ts b/apps/files_sharing/src/new/newFileRequest.ts index c5a0ca07bd1..79f9eae3098 100644 --- a/apps/files_sharing/src/new/newFileRequest.ts +++ b/apps/files_sharing/src/new/newFileRequest.ts @@ -16,7 +16,7 @@ const sharingConfig = new Config() export const entry = { id: 'file-request', - displayName: t('files', 'Create new file request'), + displayName: t('files_sharing', 'Create file request'), iconSvgInline: FileUploadSvg, order: 30, enabled(context: Folder): boolean { -- cgit v1.2.3 From 08d3fed24f778d1cf3257ce782eede5b5e8d1154 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Thu, 18 Jul 2024 14:13:43 +0200 Subject: fix(files_sharing): file request form validation and date component event Signed-off-by: skjnldsv --- apps/files_sharing/src/components/NewFileRequestDialog.vue | 1 + .../NewFileRequestDialog/NewFileRequestDialogDatePassword.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue index b943169963a..4ff31adb9ab 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -220,6 +220,7 @@ export default defineComponent({ const form = this.$refs.form as HTMLFormElement if (!form.checkValidity()) { form.reportValidity() + return } // custom destination validation diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue index a6a66c5fa0d..0eb89388122 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue @@ -34,7 +34,7 @@ :value="expirationDate" name="expirationDate" type="date" - @update:value="$emit('update:expirationDate', $event)" /> + @input="$emit('update:expirationDate', $event)" />

-- cgit v1.2.3 From 607f0b0e9a513cb317ae7c4d529285cc0cb48831 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Thu, 18 Jul 2024 15:23:28 +0200 Subject: fix(files_sharing): file request expiration date timezone Signed-off-by: skjnldsv --- apps/files_sharing/src/components/NewFileRequestDialog.vue | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'apps/files_sharing') diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue index 4ff31adb9ab..6b0bb5a1fc2 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -270,8 +270,19 @@ export default defineComponent({ async createShare() { this.loading = true + // This should never happen™ + if (this.expirationDate == null) { + throw new Error('Expiration date is missing') + } + + const year = this.expirationDate.getFullYear() + const month = (this.expirationDate.getMonth() + 1).toString().padStart(2, '0') + const day = this.expirationDate.getDate().toString().padStart(2, '0') + // Format must be YYYY-MM-DD - const expireDate = this.expirationDate ? this.expirationDate.toISOString().split('T')[0] : undefined + const expireDate = this.expirationDate + ? `${year}-${month}-${day}` + : undefined const shareUrl = generateOcsUrl('apps/files_sharing/api/v1/shares') try { const request = await axios.post(shareUrl, { -- cgit v1.2.3