aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2024-07-19 09:14:09 +0200
committerGitHub <noreply@github.com>2024-07-19 09:14:09 +0200
commit0bde47a39256dfad3baa8d3ffa275ac3d113a9d5 (patch)
tree4543f6d971922c082fd3ec32110ab42be4213786 /apps/files_sharing
parent22efc6da6a8196852e7346e9de5364fa05c6c784 (diff)
parent725736a754c81ccee17ba394de4da38f5bfa0e68 (diff)
downloadnextcloud-server-0bde47a39256dfad3baa8d3ffa275ac3d113a9d5.tar.gz
nextcloud-server-0bde47a39256dfad3baa8d3ffa275ac3d113a9d5.zip
Merge pull request #46589 from nextcloud/fix/files_sharing-file-request-followup
Diffstat (limited to 'apps/files_sharing')
-rw-r--r--apps/files_sharing/composer/composer/autoload_classmap.php1
-rw-r--r--apps/files_sharing/composer/composer/autoload_static.php1
-rw-r--r--apps/files_sharing/js/files_drop.js6
-rw-r--r--apps/files_sharing/lib/AppInfo/Application.php7
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php6
-rw-r--r--apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php55
-rw-r--r--apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php59
-rw-r--r--apps/files_sharing/src/actions/openInFilesAction.ts2
-rw-r--r--apps/files_sharing/src/components/NewFileRequestDialog.vue122
-rw-r--r--apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue40
-rw-r--r--apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue52
-rw-r--r--apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue23
-rw-r--r--apps/files_sharing/src/components/SelectShareFolderDialogue.vue6
-rw-r--r--apps/files_sharing/src/components/SharingEntryLink.vue4
-rw-r--r--apps/files_sharing/src/new/newFileRequest.ts19
-rw-r--r--apps/files_sharing/src/public-file-request.ts23
-rw-r--r--apps/files_sharing/src/services/ConfigService.ts9
-rw-r--r--apps/files_sharing/src/views/PublicAuthPrompt.vue136
-rw-r--r--apps/files_sharing/src/views/SharingDetailsTab.vue5
-rw-r--r--apps/files_sharing/templates/public.php7
-rw-r--r--apps/files_sharing/tests/Controller/ShareControllerTest.php14
21 files changed, 461 insertions, 136 deletions
diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php
index e1abddb3a64..b7a931a6228 100644
--- a/apps/files_sharing/composer/composer/autoload_classmap.php
+++ b/apps/files_sharing/composer/composer/autoload_classmap.php
@@ -59,6 +59,7 @@ return array(
'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => $baseDir . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => $baseDir . '/../lib/Listener/BeforeZipCreatedListener.php',
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
+ 'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php
index 5d2fb3bac2a..70dc7be7cdf 100644
--- a/apps/files_sharing/composer/composer/autoload_static.php
+++ b/apps/files_sharing/composer/composer/autoload_static.php
@@ -74,6 +74,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeDirectFileDownloadListener.php',
'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeZipCreatedListener.php',
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
+ 'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
diff --git a/apps/files_sharing/js/files_drop.js b/apps/files_sharing/js/files_drop.js
index fd9b796ee2c..450af078af2 100644
--- a/apps/files_sharing/js/files_drop.js
+++ b/apps/files_sharing/js/files_drop.js
@@ -22,7 +22,7 @@
// note: password not be required, the endpoint
// will recognize previous validation from the session
root: OC.getRootPath() + '/public.php/dav/files/' + $('#sharingToken').val() + '/',
- useHTTPS: OC.getProtocol() === 'https'
+ useHTTPS: OC.getProtocol() === 'https',
});
// We only process one file at a time 🤷‍♀️
@@ -47,6 +47,10 @@
data.headers = {};
}
+ if (localStorage.getItem('nick') !== null) {
+ data.headers['X-NC-Nickname'] = localStorage.getItem('nick')
+ }
+
$('#drop-upload-done-indicator').addClass('hidden');
$('#drop-upload-progress-indicator').removeClass('hidden');
diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php
index 82a5981febf..98c2d280856 100644
--- a/apps/files_sharing/lib/AppInfo/Application.php
+++ b/apps/files_sharing/lib/AppInfo/Application.php
@@ -18,6 +18,7 @@ use OCA\Files_Sharing\Helper;
use OCA\Files_Sharing\Listener\BeforeDirectFileDownloadListener;
use OCA\Files_Sharing\Listener\BeforeZipCreatedListener;
use OCA\Files_Sharing\Listener\LoadAdditionalListener;
+use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
use OCA\Files_Sharing\Listener\LoadSidebarListener;
use OCA\Files_Sharing\Listener\ShareInteractionListener;
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
@@ -34,6 +35,7 @@ use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
+use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\ICloudIdManager;
@@ -85,7 +87,7 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(GroupChangedEvent::class, GroupDisplayNameCache::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDisplayNameCache::class);
- // sidebar and files scripts
+ // Sidebar and files scripts
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
$context->registerEventListener(ShareCreatedEvent::class, ShareInteractionListener::class);
@@ -95,6 +97,9 @@ class Application extends App implements IBootstrap {
// Handle download events for view only checks
$context->registerEventListener(BeforeZipCreatedEvent::class, BeforeZipCreatedListener::class);
$context->registerEventListener(BeforeDirectFileDownloadEvent::class, BeforeDirectFileDownloadListener::class);
+
+ // File request auth
+ $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);
}
public function boot(IBootContext $context): void {
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index f2ace7b4d70..1e6750a5bce 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -596,8 +596,10 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Invalid permissions'));
}
- // Shares always require read permissions
- $permissions |= Constants::PERMISSION_READ;
+ // Shares always require read permissions OR create permissions
+ if (($permissions & Constants::PERMISSION_READ) === 0 && ($permissions & Constants::PERMISSION_CREATE) === 0) {
+ $permissions |= Constants::PERMISSION_READ;
+ }
if ($node instanceof \OCP\Files\File) {
// Single file shares should never have delete or create permissions
diff --git a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
index 8f5ea9fd0be..477bc9f82ce 100644
--- a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
+++ b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
@@ -19,6 +19,7 @@ use OCP\AppFramework\Http\Template\LinkMenuAction;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\Template\SimpleMenuAction;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
use OCP\Constants;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
@@ -37,39 +38,20 @@ use OCP\Template;
use OCP\Util;
class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider {
- private IUserManager $userManager;
- private IAccountManager $accountManager;
- private IPreview $previewManager;
- protected FederatedShareProvider $federatedShareProvider;
- private IURLGenerator $urlGenerator;
- private IEventDispatcher $eventDispatcher;
- private IL10N $l10n;
- private Defaults $defaults;
- private IConfig $config;
- private IRequest $request;
public function __construct(
- IUserManager $userManager,
- IAccountManager $accountManager,
- IPreview $previewManager,
- FederatedShareProvider $federatedShareProvider,
- IUrlGenerator $urlGenerator,
- IEventDispatcher $eventDispatcher,
- IL10N $l10n,
- Defaults $defaults,
- IConfig $config,
- IRequest $request
+ private IUserManager $userManager,
+ private IAccountManager $accountManager,
+ private IPreview $previewManager,
+ protected FederatedShareProvider $federatedShareProvider,
+ private IUrlGenerator $urlGenerator,
+ private IEventDispatcher $eventDispatcher,
+ private IL10N $l10n,
+ private Defaults $defaults,
+ private IConfig $config,
+ private IRequest $request,
+ private IInitialState $initialState,
) {
- $this->userManager = $userManager;
- $this->accountManager = $accountManager;
- $this->previewManager = $previewManager;
- $this->federatedShareProvider = $federatedShareProvider;
- $this->urlGenerator = $urlGenerator;
- $this->eventDispatcher = $eventDispatcher;
- $this->l10n = $l10n;
- $this->defaults = $defaults;
- $this->config = $config;
- $this->request = $request;
}
public function shouldRespond(IShare $share): bool {
@@ -91,11 +73,19 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
if ($ownerName->getScope() === IAccountManager::SCOPE_PUBLISHED) {
$shareTmpl['owner'] = $owner->getUID();
$shareTmpl['shareOwner'] = $owner->getDisplayName();
+ $this->initialState->provideInitialState('owner', $shareTmpl['owner']);
+ $this->initialState->provideInitialState('ownerDisplayName', $shareTmpl['shareOwner']);
}
}
+ // Provide initial state
+ $this->initialState->provideInitialState('label', $share->getLabel());
+ $this->initialState->provideInitialState('note', $share->getNote());
+ $this->initialState->provideInitialState('filename', $shareNode->getName());
+
$shareTmpl['filename'] = $shareNode->getName();
$shareTmpl['directory_path'] = $share->getTarget();
+ $shareTmpl['label'] = $share->getLabel();
$shareTmpl['note'] = $share->getNote();
$shareTmpl['mimetype'] = $shareNode->getMimetype();
$shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($shareNode->getMimetype());
@@ -240,6 +230,11 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
$response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['shareOwner']]));
}
+ // If the share has a label, use it as the title
+ if ($shareTmpl['label'] !== '') {
+ $response->setHeaderTitle($shareTmpl['label']);
+ }
+
$isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== Constants::PERMISSION_CREATE;
if ($isNoneFileDropFolder && !$share->getHideDownload()) {
diff --git a/apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php b/apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php
new file mode 100644
index 00000000000..f1e054c7ee5
--- /dev/null
+++ b/apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Files_Sharing\Listener;
+
+use OCA\Files_Sharing\AppInfo\Application;
+use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Share\IManager;
+use OCP\Util;
+
+/** @template-implements IEventListener<BeforeTemplateRenderedEvent> */
+class LoadPublicFileRequestAuthListener implements IEventListener {
+ public function __construct(
+ private IManager $shareManager,
+ ) {
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof BeforeTemplateRenderedEvent) {
+ return;
+ }
+
+ // Make sure we are on a public page rendering
+ if ($event->getResponse()->getRenderAs() !== TemplateResponse::RENDER_AS_PUBLIC) {
+ return;
+ }
+
+ $token = $event->getResponse()->getParams()['sharingToken'] ?? null;
+ if ($token === null || $token === '') {
+ return;
+ }
+
+ // Check if the share is a file request
+ $isFileRequest = false;
+ try {
+ $share = $this->shareManager->getShareByToken($token);
+ $attributes = $share->getAttributes();
+ if ($attributes === null) {
+ return;
+ }
+
+ $isFileRequest = $attributes->getAttribute('fileRequest', 'enabled') === true;
+ } catch (\Exception $e) {
+ // Ignore, this is not a file request or the share does not exist
+ }
+
+ if ($isFileRequest) {
+ // Add the script to the public page
+ Util::addScript(Application::APP_ID, 'public-file-request');
+ }
+ }
+}
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 35cd4395290..6b0bb5a1fc2 100644
--- a/apps/files_sharing/src/components/NewFileRequestDialog.vue
+++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue
@@ -50,8 +50,22 @@
<!-- Controls -->
<template #actions>
+ <!-- Back -->
+ <NcButton v-show="currentStep === STEP.SECOND"
+ :aria-label="t('files_sharing', 'Previous step')"
+ :disabled="loading"
+ data-cy-file-request-dialog-controls="back"
+ type="tertiary"
+ @click="currentStep = STEP.FIRST">
+ {{ t('files_sharing', 'Previous step') }}
+ </NcButton>
+
+ <!-- Align right -->
+ <span class="dialog__actions-separator" />
+
<!-- Cancel the creation -->
- <NcButton :aria-label="t('files_sharing', 'Cancel')"
+ <NcButton v-if="currentStep !== STEP.LAST"
+ :aria-label="t('files_sharing', 'Cancel')"
:disabled="loading"
:title="t('files_sharing', 'Cancel the file request creation')"
data-cy-file-request-dialog-controls="cancel"
@@ -60,17 +74,15 @@
{{ t('files_sharing', 'Cancel') }}
</NcButton>
- <!-- Align right -->
- <span class="dialog__actions-separator" />
-
- <!-- Back -->
- <NcButton v-show="currentStep === STEP.SECOND"
- :aria-label="t('files_sharing', 'Previous step')"
+ <!-- Cancel email and just close -->
+ <NcButton v-else-if="emails.length !== 0"
+ :aria-label="t('files_sharing', 'Close without sending emails')"
:disabled="loading"
- data-cy-file-request-dialog-controls="back"
+ :title="t('files_sharing', 'Close without sending emails')"
+ data-cy-file-request-dialog-controls="cancel"
type="tertiary"
- @click="currentStep = STEP.FIRST">
- {{ t('files_sharing', 'Previous step') }}
+ @click="onCancel">
+ {{ t('files_sharing', 'Close') }}
</NcButton>
<!-- Next -->
@@ -115,7 +127,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 +182,8 @@ export default defineComponent({
setup() {
return {
STEP,
-
- n: translatePlural,
- t: translate,
+ n,
+ t,
isShareByMailEnabled: sharingConfig.isMailShareAllowed,
}
@@ -198,9 +209,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', 'Send email and close', 'Send {count} emails and close', this.emails.length, { count: this.emails.length })
},
},
@@ -209,13 +220,14 @@ export default defineComponent({
const form = this.$refs.form as HTMLFormElement
if (!form.checkValidity()) {
form.reportValidity()
+ return
}
// custom destination validation
// 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
}
@@ -239,26 +251,44 @@ 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
}
- await this.setShareEmails()
- await this.sendEmails()
- showSuccess(this.t('files_sharing', 'File request created and emails sent'))
+ if (sharingConfig.isMailShareAllowed && this.emails.length > 0) {
+ await this.setShareEmails()
+ await this.sendEmails()
+ 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(t('files_sharing', 'File request created'))
+ }
+
this.$emit('close')
},
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<OCSResponse>(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,
@@ -294,8 +324,8 @@ export default defineComponent({
const errorMessage = (error as AxiosError<OCSResponse>)?.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
@@ -320,6 +350,11 @@ export default defineComponent({
value: this.emails,
key: 'emails',
scope: 'shareWith',
+ },
+ {
+ value: true,
+ key: 'enabled',
+ scope: 'fileRequest',
}]),
})
@@ -366,8 +401,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 })
},
@@ -375,10 +410,9 @@ export default defineComponent({
})
</script>
-<style scoped lang="scss">
+<style lang="scss">
.file-request-dialog {
- --margin: 36px;
- --secondary-margin: 18px;
+ --margin: 18px;
&__header {
margin: 0 var(--margin);
@@ -387,35 +421,45 @@ export default defineComponent({
&__form {
position: relative;
overflow: auto;
- padding: var(--secondary-margin) var(--margin);
+ padding: var(--margin) var(--margin);
// overlap header bottom padding
- margin-top: calc(-1 * var(--secondary-margin));
+ margin-top: calc(-1 * var(--margin));
}
- :deep(fieldset) {
+ fieldset {
display: flex;
flex-direction: column;
width: 100%;
- margin-top: var(--secondary-margin);
+ margin-top: var(--margin);
- :deep(legend) {
+ legend {
display: flex;
align-items: center;
width: 100%;
}
}
- :deep(.dialog__actions) {
+ // Using a NcNoteCard was a bit much sometimes.
+ // Using a simple paragraph instead does it.
+ &__info {
+ color: var(--color-text-maxcontrast);
+ padding-block: 4px;
+ display: flex;
+ align-items: center;
+ .file-request-dialog__info-icon {
+ margin-inline-end: 8px;
+ }
+ }
+
+ .dialog__actions {
width: auto;
margin-inline: 12px;
- // align left and remove margin
- margin-left: 0;
span.dialog__actions-separator {
margin-left: auto;
}
}
- :deep(.input-field__helper-text-message) {
+ .input-field__helper-text-message {
// reduce helper text standing out
color: var(--color-text-maxcontrast);
}
diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue
index 9bb1863e1d1..0eb89388122 100644
--- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue
+++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue
@@ -12,17 +12,13 @@
<!-- Expiration date -->
<fieldset class="file-request-dialog__expiration" data-cy-file-request-dialog-fieldset="expiration">
- <NcNoteCard v-if="defaultExpireDateEnforced" type="info">
- {{ t('files_sharing', 'Your administrator has enforced a default expiration date with a maximum {days} days.', { days: defaultExpireDate }) }}
- </NcNoteCard>
-
<!-- Enable expiration -->
<legend>{{ t('files_sharing', 'When should the request expire?') }}</legend>
<NcCheckboxRadioSwitch v-show="!defaultExpireDateEnforced"
:checked="defaultExpireDateEnforced || expirationDate !== null"
:disabled="disabled || defaultExpireDateEnforced"
@update:checked="onToggleDeadline">
- {{ t('files_sharing', 'Set a submission expirationDate') }}
+ {{ t('files_sharing', 'Set a submission expiration date') }}
</NcCheckboxRadioSwitch>
<!-- Date picker -->
@@ -38,15 +34,16 @@
:value="expirationDate"
name="expirationDate"
type="date"
- @update:value="$emit('update:expirationDate', $event)" />
+ @input="$emit('update:expirationDate', $event)" />
+
+ <p v-if="defaultExpireDateEnforced" class="file-request-dialog__info">
+ <IconInfo :size="18" class="file-request-dialog__info-icon" />
+ {{ t('files_sharing', 'Your administrator has enforced a {count} days expiration policy.', { count: defaultExpireDate }) }}
+ </p>
</fieldset>
<!-- Password -->
<fieldset class="file-request-dialog__password" data-cy-file-request-dialog-fieldset="password">
- <NcNoteCard v-if="enforcePasswordForPublicLink" type="info">
- {{ t('files_sharing', 'Your administrator has enforced a password protection.') }}
- </NcNoteCard>
-
<!-- Enable password -->
<legend>{{ t('files_sharing', 'What password should be used for the request?') }}</legend>
<NcCheckboxRadioSwitch v-show="!enforcePasswordForPublicLink"
@@ -75,13 +72,18 @@
</template>
</NcButton>
</div>
+
+ <p v-if="enforcePasswordForPublicLink" class="file-request-dialog__info">
+ <IconInfo :size="18" class="file-request-dialog__info-icon" />
+ {{ t('files_sharing', 'Your administrator has enforced a password protection.') }}
+ </p>
</fieldset>
</div>
</template>
<script lang="ts">
import { defineComponent, type PropType } from 'vue'
-import { translate } from '@nextcloud/l10n'
+import { t } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
@@ -89,6 +91,7 @@ import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePic
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
+import IconInfo from 'vue-material-design-icons/Information.vue'
import IconPasswordGen from 'vue-material-design-icons/AutoFix.vue'
import Config from '../../services/ConfigService'
@@ -100,6 +103,7 @@ export default defineComponent({
name: 'NewFileRequestDialogDatePassword',
components: {
+ IconInfo,
IconPasswordGen,
NcButton,
NcCheckboxRadioSwitch,
@@ -133,7 +137,7 @@ export default defineComponent({
setup() {
return {
- t: translate,
+ t,
// Default expiration date if defaultExpireDateEnabled is true
defaultExpireDate: sharingConfig.defaultExpireDate,
@@ -159,19 +163,19 @@ export default defineComponent({
computed: {
passwordAndExpirationSummary(): string {
if (this.expirationDate && this.password) {
- return this.t('files_sharing', 'The request will expire on {date} at midnight and will be password protected.', {
+ return t('files_sharing', 'The request will expire on {date} at midnight and will be password protected.', {
date: this.expirationDate.toLocaleDateString(),
})
}
if (this.expirationDate) {
- return this.t('files_sharing', 'The request will expire on {date} at midnight.', {
+ return t('files_sharing', 'The request will expire on {date} at midnight.', {
date: this.expirationDate.toLocaleDateString(),
})
}
if (this.password) {
- return this.t('files_sharing', 'The request will be password protected.')
+ return t('files_sharing', 'The request will be password protected.')
}
return ''
@@ -232,5 +236,11 @@ export default defineComponent({
display: flex;
align-items: flex-start;
gap: 8px;
+ // Compensate label gab with legend
+ margin-top: 12px;
+ > div {
+ // Force margin to 0 as we handle it above
+ margin: 0;
+ }
}
</style>
diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue
index 19753b8e6e2..e626cb31751 100644
--- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue
+++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue
@@ -7,7 +7,7 @@
<div>
<!-- Request note -->
<NcNoteCard type="success">
- {{ t('files_sharing', 'Once created, you can share the link below to allow people to upload files to your directory.') }}
+ {{ t('files_sharing', 'You can now share the link below to allow people to upload files to your directory.') }}
</NcNoteCard>
<!-- Copy share link -->
@@ -18,7 +18,7 @@
:show-trailing-button="true"
:trailing-button-label="t('files_sharing', 'Copy to clipboard')"
@click="copyShareLink"
- @click-trailing-button="copyShareLink">
+ @trailing-button-click="copyShareLink">
<template #trailing-button-icon>
<IconCheck v-if="isCopied" :size="20" />
<IconClipboard v-else :size="20" />
@@ -32,7 +32,8 @@
:placeholder="t('files_sharing', 'Enter an email address or paste a list')"
type="email"
@keypress.enter.stop="addNewEmail"
- @paste.stop.prevent="onPasteEmails" />
+ @paste.stop.prevent="onPasteEmails"
+ @focusout.native="addNewEmail" />
<!-- Email list -->
<div v-if="emails.length > 0" class="file-request-dialog__emails">
@@ -44,9 +45,10 @@
<template #icon>
<NcAvatar :disable-menu="true"
:disable-tooltip="true"
- :is-guest="true"
- :size="24"
- :user="mail" />
+ :display-name="mail"
+ :is-no-user="true"
+ :show-user-status="false"
+ :size="24" />
</template>
</NcChip>
</div>
@@ -61,7 +63,7 @@ import Share from '../../models/Share'
import { defineComponent } from 'vue'
import { generateUrl, getBaseUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs'
-import { translate, translatePlural } from '@nextcloud/l10n'
+import { n, t } from '@nextcloud/l10n'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
@@ -70,7 +72,7 @@ import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcChip from '@nextcloud/vue/dist/Components/NcChip.js'
import IconCheck from 'vue-material-design-icons/Check.vue'
-import IconClipboard from 'vue-material-design-icons/Clipboard.vue'
+import IconClipboard from 'vue-material-design-icons/ClipboardText.vue'
export default defineComponent({
name: 'NewFileRequestDialogFinish',
@@ -104,8 +106,7 @@ export default defineComponent({
setup() {
return {
- n: translatePlural,
- t: translate,
+ n, t,
}
},
@@ -131,13 +132,13 @@ export default defineComponent({
if (!navigator.clipboard) {
// Clipboard API not available
- window.prompt(this.t('files_sharing', 'Automatically copying failed, please copy the share link manually'), this.shareLink)
+ window.prompt(t('files_sharing', 'Automatically copying failed, please copy the share link manually'), this.shareLink)
return
}
await navigator.clipboard.writeText(this.shareLink)
- showSuccess(this.t('files_sharing', 'Link copied to clipboard'))
+ showSuccess(t('files_sharing', 'Link copied to clipboard'))
this.isCopied = true
event.target?.select?.()
@@ -147,7 +148,15 @@ export default defineComponent({
},
addNewEmail(e: KeyboardEvent) {
+ if (this.email.trim() === '') {
+ return
+ }
+
if (e.target instanceof HTMLInputElement) {
+ // Reset the custom validity
+ e.target.setCustomValidity('')
+
+ // Check if the field is valid
if (e.target.checkValidity() === false) {
e.target.reportValidity()
return
@@ -155,13 +164,14 @@ export default defineComponent({
// The email is already in the list
if (this.emails.includes(this.email.trim())) {
- e.target.setCustomValidity(this.t('files_sharing', 'Email already added'))
+ e.target.setCustomValidity(t('files_sharing', 'Email already added'))
e.target.reportValidity()
return
}
+ // Check if the email is valid
if (!this.isValidEmail(this.email.trim())) {
- e.target.setCustomValidity(this.t('files_sharing', 'Invalid email address'))
+ e.target.setCustomValidity(t('files_sharing', 'Invalid email address'))
e.target.reportValidity()
return
}
@@ -188,24 +198,24 @@ export default defineComponent({
// Warn about invalid emails
if (invalidEmails.length > 0) {
- showError(this.n('files_sharing', 'The following email address is not valid: {emails}', 'The following email addresses are not valid: {emails}', invalidEmails.length, { emails: invalidEmails.join(', ') }))
+ showError(n('files_sharing', 'The following email address is not valid: {emails}', 'The following email addresses are not valid: {emails}', invalidEmails.length, { emails: invalidEmails.join(', ') }))
}
// Warn about duplicate emails
if (duplicateEmails.length > 0) {
- showError(this.n('files_sharing', '1 email address already added', '{count} email addresses already added', duplicateEmails.length, { count: duplicateEmails.length }))
+ showError(n('files_sharing', '1 email address already added', '{count} email addresses already added', duplicateEmails.length, { count: duplicateEmails.length }))
}
if (validEmails.length > 0) {
- showSuccess(this.n('files_sharing', '1 email address added', '{count} email addresses added', validEmails.length, { count: validEmails.length }))
+ showSuccess(n('files_sharing', '1 email address added', '{count} email addresses added', validEmails.length, { count: validEmails.length }))
}
this.email = ''
},
- isValidEmail(email) {
- const regExpEmail = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
- return regExpEmail.test(email)
+ // No need to have a fancy regex, just check for an @
+ isValidEmail(email: string): boolean {
+ return email.includes('@')
},
},
})
@@ -213,7 +223,7 @@ export default defineComponent({
<style scoped>
.input-field,
.file-request-dialog__emails {
- margin-top: var(--secondary-margin);
+ margin-top: var(--margin);
}
.file-request-dialog__emails {
diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue
index c995cdc2442..805b13fdf95 100644
--- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue
+++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue
@@ -26,7 +26,6 @@
</legend>
<NcTextField :value="destination"
:disabled="disabled"
- :helper-text="t('files_sharing', 'The uploaded files are visible only to you unless you choose to share them.')"
:label="t('files_sharing', 'Upload destination')"
:minlength="2/* cannot share root */"
:placeholder="t('files_sharing', 'Select a destination')"
@@ -42,6 +41,11 @@
@trailing-button-click="$emit('update:destination', '')">
<IconFolder :size="18" />
</NcTextField>
+
+ <p class="file-request-dialog__info">
+ <IconLock :size="18" class="file-request-dialog__info-icon" />
+ {{ t('files_sharing', 'The uploaded files are visible only to you unless you choose to share them.') }}
+ </p>
</fieldset>
<!-- Request note -->
@@ -56,6 +60,11 @@
:required="false"
name="note"
@update:value="$emit('update:note', $event)" />
+
+ <p class="file-request-dialog__info">
+ <IconInfo :size="18" class="file-request-dialog__info-icon" />
+ {{ t('files_sharing', 'You can add links, date or any other information that will help the recipient understand what you are requesting.') }}
+ </p>
</fieldset>
</div>
</template>
@@ -66,9 +75,11 @@ import type { Folder, Node } from '@nextcloud/files'
import { defineComponent } from 'vue'
import { getFilePickerBuilder } from '@nextcloud/dialogs'
-import { translate } from '@nextcloud/l10n'
+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,
},
@@ -113,17 +126,17 @@ export default defineComponent({
setup() {
return {
- t: translate,
+ t,
}
},
methods: {
onPickDestination() {
- const filepicker = getFilePickerBuilder(this.t('files_sharing', 'Select a destination'))
+ const filepicker = getFilePickerBuilder(t('files_sharing', 'Select a destination'))
.addMimeTypeFilter('httpd/unix-directory')
.allowDirectories(true)
.addButton({
- label: this.t('files_sharing', 'Select'),
+ label: t('files_sharing', 'Select'),
callback: this.onPickedDestination,
})
.setFilter(node => node.path !== '/')
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/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue
index 7fad50ebc95..ad3f4033b28 100644
--- a/apps/files_sharing/src/components/SharingEntryLink.vue
+++ b/apps/files_sharing/src/components/SharingEntryLink.vue
@@ -25,8 +25,8 @@
</div>
<!-- clipboard -->
- <NcActions v-if="share && !isEmailShareType && share.token" ref="copyButton" class="sharing-entry__copy">
- <NcActionButton :title="copyLinkTooltip"
+ <NcActions v-if="share && (!isEmailShareType || isFileRequest) && share.token" ref="copyButton" class="sharing-entry__copy">
+ <NcActionButton :title="copyLinkTooltip"
:aria-label="copyLinkTooltip"
@click.prevent="copyLink">
<template #icon>
diff --git a/apps/files_sharing/src/new/newFileRequest.ts b/apps/files_sharing/src/new/newFileRequest.ts
index b7e5b3f2144..79f9eae3098 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'),
+ displayName: t('files_sharing', 'Create 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/public-file-request.ts b/apps/files_sharing/src/public-file-request.ts
new file mode 100644
index 00000000000..763c4f60624
--- /dev/null
+++ b/apps/files_sharing/src/public-file-request.ts
@@ -0,0 +1,23 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { spawnDialog } from '@nextcloud/dialogs'
+import { defineAsyncComponent } from 'vue'
+import logger from './services/logger'
+
+const nick = localStorage.getItem('nick')
+const publicAuthPromptShown = localStorage.getItem('publicAuthPromptShown')
+
+// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it
+// We still show the prompt if the user has a nickname to double check
+if (!nick || !publicAuthPromptShown) {
+ spawnDialog(
+ defineAsyncComponent(() => import('./views/PublicAuthPrompt.vue')),
+ {},
+ () => localStorage.setItem('publicAuthPromptShown', 'true'),
+ )
+} else {
+ logger.debug(`Public auth prompt already shown. Current nickname is '${nick}'`)
+}
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
@@ -211,13 +211,20 @@ export default class Config {
}
/**
+ * Is public sharing enabled ?
+ */
+ get isPublicShareAllowed(): boolean {
+ return this._capabilities?.files_sharing?.public?.enabled === true
+ }
+
+ /**
* Is sharing my mail (link share) enabled ?
*/
get isMailShareAllowed(): boolean {
// 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
}
/**
diff --git a/apps/files_sharing/src/views/PublicAuthPrompt.vue b/apps/files_sharing/src/views/PublicAuthPrompt.vue
new file mode 100644
index 00000000000..a929afffefb
--- /dev/null
+++ b/apps/files_sharing/src/views/PublicAuthPrompt.vue
@@ -0,0 +1,136 @@
+<!--
+ - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<template>
+ <NcDialog class="public-auth-prompt"
+ dialog-classes="public-auth-prompt__dialog"
+ :can-close="false"
+ :name="dialogName">
+ <h3 v-if="owner" class="public-auth-prompt__subtitle">
+ {{ t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) }}
+ </h3>
+
+ <!-- Header -->
+ <NcNoteCard type="info" class="public-auth-prompt__header">
+ <p id="public-auth-prompt-dialog-description" class="public-auth-prompt__description">
+ {{ t('files_sharing', 'To upload files, you need to provide your name first.') }}
+ </p>
+ </NcNoteCard>
+
+ <!-- Form -->
+ <form ref="form"
+ aria-describedby="public-auth-prompt-dialog-description"
+ class="public-auth-prompt__form"
+ @submit.prevent.stop="">
+ <NcTextField ref="input"
+ class="public-auth-prompt__input"
+ :label="t('files_sharing', 'Enter your name')"
+ name="name"
+ :required="true"
+ :minlength="2"
+ :value.sync="name" />
+ </form>
+
+ <!-- Submit -->
+ <template #actions>
+ <NcButton ref="submit"
+ :disabled="name.trim() === ''"
+ @click="onSubmit">
+ {{ t('files_sharing', 'Submit name') }}
+ </NcButton>
+ </template>
+ </NcDialog>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+import { t } from '@nextcloud/l10n'
+
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
+import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
+import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+import { loadState } from '@nextcloud/initial-state'
+
+export default defineComponent({
+ name: 'PublicAuthPrompt',
+
+ components: {
+ NcButton,
+ NcDialog,
+ NcNoteCard,
+ NcTextField,
+ },
+
+ setup() {
+ return {
+ t,
+
+ owner: loadState('files_sharing', 'owner', ''),
+ ownerDisplayName: loadState('files_sharing', 'ownerDisplayName', ''),
+ label: loadState('files_sharing', 'label', ''),
+ note: loadState('files_sharing', 'note', ''),
+ filename: loadState('files_sharing', 'filename', ''),
+ }
+ },
+
+ data() {
+ return {
+ name: '',
+ }
+ },
+
+ computed: {
+ dialogName() {
+ return this.t('files_sharing', 'Upload files to {folder}', { folder: this.label || this.filename })
+ },
+ },
+
+ beforeMount() {
+ // Pre-load the name from local storage if already set by another app
+ // like Talk, Colabora or Text...
+ const talkNick = localStorage.getItem('nick')
+ if (talkNick) {
+ this.name = talkNick
+ }
+ },
+
+ methods: {
+ onSubmit() {
+ const form = this.$refs.form as HTMLFormElement
+ if (!form.checkValidity()) {
+ form.reportValidity()
+ return
+ }
+
+ if (this.name.trim() === '') {
+ return
+ }
+
+ localStorage.setItem('nick', this.name)
+ this.$emit('close')
+ },
+ },
+})
+</script>
+<style lang="scss">
+.public-auth-prompt {
+ &__subtitle {
+ // Smaller than dialog title
+ font-size: 16px;
+ margin-block: 12px;
+ }
+
+ &__header {
+ // Fix extra margin generating an unwanted gap
+ margin-block: 12px;
+ }
+
+ &__form {
+ // Double the margin of the header
+ margin-block: 24px;
+ }
+}
+</style>
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
diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php
index 7de2bd3075e..7620d309ff6 100644
--- a/apps/files_sharing/templates/public.php
+++ b/apps/files_sharing/templates/public.php
@@ -27,6 +27,7 @@
<input type="hidden" name="mimetypeIcon" value="<?php p(\OC::$server->getMimeTypeDetector()->mimeTypeIcon($_['mimetype'])); ?>" id="mimetypeIcon">
<input type="hidden" name="hideDownload" value="<?php p($_['hideDownload'] ? 'true' : 'false'); ?>" id="hideDownload">
<input type="hidden" id="disclaimerText" value="<?php p($_['disclaimer']) ?>">
+
<?php
$upload_max_filesize = OC::$server->get(\bantu\IniGetWrapper\IniGetWrapper::class)->getBytes('upload_max_filesize');
$post_max_size = OC::$server->get(\bantu\IniGetWrapper\IniGetWrapper::class)->getBytes('post_max_size');
@@ -102,11 +103,11 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
class="emptycontent <?php if (!empty($_['note'])) { ?>has-note<?php } ?>">
<?php if ($_['shareOwner']) { ?>
<div id="displayavatar"><div class="avatardiv"></div></div>
- <h2><?php p($l->t('Upload files to %s', [$_['shareOwner']])) ?></h2>
- <p><span class="icon-folder"></span> <?php p($_['filename']) ?></p>
+ <h2><?php p($l->t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?></h2>
+ <p><?php p($l->t('%s shared a folder with you.', [$_['shareOwner']])) ?></p>
<?php } else { ?>
<div id="displayavatar"><span class="icon-folder"></span></div>
- <h2><?php p($l->t('Upload files to %s', [$_['filename']])) ?></h2>
+ <h2><?php p($l->t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?></h2>
<?php } ?>
<?php if (empty($_['note']) === false) { ?>
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();