Merge pull request #46589 from nextcloud/fix/files_sharing-file-request-followup

This commit is contained in:
John Molakvoæ 2024-07-19 09:14:09 +02:00 committed by GitHub
commit 0bde47a392
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 793 additions and 220 deletions

View file

@ -87,6 +87,7 @@ $server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, funct
$view = new \OC\Files\View($node->getPath());
$filesDropPlugin->setView($view);
$filesDropPlugin->setShare($share);
return $view;
});

View file

@ -116,6 +116,7 @@ $server = $serverFactory->createServer($baseuri, $requestUri, $authPlugin, funct
$view = new View($node->getPath());
$filesDropPlugin->setView($view);
$filesDropPlugin->setShare($share);
return $view;
});

View file

@ -6,6 +6,7 @@
namespace OCA\DAV\Files\Sharing;
use OC\Files\View;
use OCP\Share\IShare;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
@ -16,20 +17,19 @@ use Sabre\HTTP\ResponseInterface;
*/
class FilesDropPlugin extends ServerPlugin {
/** @var View */
private $view;
private ?View $view = null;
private ?IShare $share = null;
private bool $enabled = false;
/** @var bool */
private $enabled = false;
/**
* @param View $view
*/
public function setView($view) {
public function setView(View $view): void {
$this->view = $view;
}
public function enable() {
public function setShare(IShare $share): void {
$this->share = $share;
}
public function enable(): void {
$this->enabled = true;
}
@ -42,25 +42,51 @@ class FilesDropPlugin extends ServerPlugin {
* @return void
* @throws MethodNotAllowed
*/
public function initialize(\Sabre\DAV\Server $server) {
public function initialize(\Sabre\DAV\Server $server): void {
$server->on('beforeMethod:*', [$this, 'beforeMethod'], 999);
$this->enabled = false;
}
public function beforeMethod(RequestInterface $request, ResponseInterface $response) {
if (!$this->enabled) {
public function beforeMethod(RequestInterface $request, ResponseInterface $response): void {
if (!$this->enabled || $this->share === null || $this->view === null) {
return;
}
// Only allow file drop
if ($request->getMethod() !== 'PUT') {
throw new MethodNotAllowed('Only PUT is allowed on files drop');
}
// Always upload at the root level
$path = explode('/', $request->getPath());
$path = array_pop($path);
// Extract the attributes for the file request
$isFileRequest = false;
$attributes = $this->share->getAttributes();
$nickName = $request->getHeader('X-NC-Nickname');
if ($attributes !== null) {
$isFileRequest = $attributes->getAttribute('fileRequest', 'enabled') === true;
}
// We need a valid nickname for file requests
if ($isFileRequest && ($nickName == null || trim($nickName) === '')) {
throw new MethodNotAllowed('Nickname is required for file requests');
}
// If this is a file request we need to create a folder for the user
if ($isFileRequest) {
// Check if the folder already exists
if (!($this->view->file_exists($nickName) === true)) {
$this->view->mkdir($nickName);
}
// Put all files in the subfolder
$path = $nickName . '/' . $path;
}
$newName = \OC_Helper::buildNotExistingFileNameForView('/', $path, $this->view);
$url = $request->getBaseUrl() . $newName;
$request->setUrl($url);
}
}

View file

@ -7,6 +7,8 @@ namespace OCA\DAV\Tests\Files\Sharing;
use OC\Files\View;
use OCA\DAV\Files\Sharing\FilesDropPlugin;
use OCP\Share\IAttributes;
use OCP\Share\IShare;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Server;
use Sabre\HTTP\RequestInterface;
@ -18,6 +20,9 @@ class FilesDropPluginTest extends TestCase {
/** @var View|\PHPUnit\Framework\MockObject\MockObject */
private $view;
/** @var IShare|\PHPUnit\Framework\MockObject\MockObject */
private $share;
/** @var Server|\PHPUnit\Framework\MockObject\MockObject */
private $server;
@ -34,6 +39,7 @@ class FilesDropPluginTest extends TestCase {
parent::setUp();
$this->view = $this->createMock(View::class);
$this->share = $this->createMock(IShare::class);
$this->server = $this->createMock(Server::class);
$this->plugin = new FilesDropPlugin();
@ -42,6 +48,11 @@ class FilesDropPluginTest extends TestCase {
$this->response->expects($this->never())
->method($this->anything());
$attributes = $this->createMock(IAttributes::class);
$this->share->expects($this->any())
->method('getAttributes')
->willReturn($attributes);
}
public function testInitialize(): void {
@ -69,6 +80,7 @@ class FilesDropPluginTest extends TestCase {
public function testValid(): void {
$this->plugin->enable();
$this->plugin->setView($this->view);
$this->plugin->setShare($this->share);
$this->request->method('getMethod')
->willReturn('PUT');
@ -93,6 +105,7 @@ class FilesDropPluginTest extends TestCase {
public function testFileAlreadyExistsValid(): void {
$this->plugin->enable();
$this->plugin->setView($this->view);
$this->plugin->setShare($this->share);
$this->request->method('getMethod')
->willReturn('PUT');
@ -122,6 +135,7 @@ class FilesDropPluginTest extends TestCase {
public function testNoMKCOL(): void {
$this->plugin->enable();
$this->plugin->setView($this->view);
$this->plugin->setShare($this->share);
$this->request->method('getMethod')
->willReturn('MKCOL');
@ -134,6 +148,7 @@ class FilesDropPluginTest extends TestCase {
public function testNoSubdirPut(): void {
$this->plugin->enable();
$this->plugin->setView($this->view);
$this->plugin->setShare($this->share);
$this->request->method('getMethod')
->willReturn('PUT');

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()) {

View file

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

View file

@ -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) => [

View file

@ -50,19 +50,6 @@
<!-- Controls -->
<template #actions>
<!-- Cancel the creation -->
<NcButton :aria-label="t('files_sharing', 'Cancel')"
:disabled="loading"
:title="t('files_sharing', 'Cancel the file request creation')"
data-cy-file-request-dialog-controls="cancel"
type="tertiary"
@click="onCancel">
{{ 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')"
@ -73,6 +60,31 @@
{{ t('files_sharing', 'Previous step') }}
</NcButton>
<!-- Align right -->
<span class="dialog__actions-separator" />
<!-- Cancel the creation -->
<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"
type="tertiary"
@click="onCancel">
{{ t('files_sharing', 'Cancel') }}
</NcButton>
<!-- Cancel email and just close -->
<NcButton v-else-if="emails.length !== 0"
:aria-label="t('files_sharing', 'Close without sending emails')"
:disabled="loading"
:title="t('files_sharing', 'Close without sending emails')"
data-cy-file-request-dialog-controls="cancel"
type="tertiary"
@click="onCancel">
{{ t('files_sharing', 'Close') }}
</NcButton>
<!-- Next -->
<NcButton v-if="currentStep !== STEP.LAST"
:aria-label="t('files_sharing', 'Continue')"
@ -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);
}

View file

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

View file

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

View file

@ -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 !== '/')

View file

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

View file

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

View file

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

View file

@ -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}'`)
}

View file

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

View file

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

View file

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

View file

@ -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) { ?>

View file

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

View file

@ -7,9 +7,6 @@ REPODIR=`git rev-parse --show-toplevel`
cd $REPODIR
# Comments files plugin
node node_modules/handlebars/bin/handlebars -n OCA.Comments.Templates apps/comments/src/templates -f apps/comments/src/templates.js
# Settings
node node_modules/handlebars/bin/handlebars -n OC.Settings.Templates apps/settings/js/templates -f apps/settings/js/templates.js

View file

@ -15,7 +15,7 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
/**
* @When Dropping file :path with :content
*/
public function droppingFileWith($path, $content) {
public function droppingFileWith($path, $content, $nickName = null) {
$client = new Client();
$options = [];
if (count($this->lastShareData->data->element) > 0) {
@ -25,11 +25,16 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
}
$base = substr($this->baseUrl, 0, -4);
$fullUrl = $base . "/public.php/dav/files/$token/$path";
$fullUrl = str_replace('//', '/', $base . "/public.php/dav/files/$token/$path");
$options['headers'] = [
'X-REQUESTED-WITH' => 'XMLHttpRequest'
];
if ($nickName) {
$options['headers']['X-NC-NICKNAME'] = $nickName;
}
$options['body'] = \GuzzleHttp\Psr7\Utils::streamFor($content);
try {
@ -38,6 +43,15 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
$this->response = $e->getResponse();
}
}
/**
* @When Dropping file :path with :content as :nickName
*/
public function droppingFileWithAs($path, $content, $nickName) {
$this->droppingFileWith($path, $content, $nickName);
}
/**
* @When Creating folder :folder in drop
@ -52,7 +66,7 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
}
$base = substr($this->baseUrl, 0, -4);
$fullUrl = $base . "/public.php/dav/files/$token/$folder";
$fullUrl = str_replace('//', '/', $base . "/public.php/dav/files/$token/$folder");
$options['headers'] = [
'X-REQUESTED-WITH' => 'XMLHttpRequest'

View file

@ -59,3 +59,34 @@ Feature: FilesDrop
| permissions | 4 |
When Creating folder "folder" in drop
Then the HTTP status code should be "405"
Scenario: Files request drop
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
And as "user0" creating a share with
| path | drop |
| shareType | 4 |
| permissions | 4 |
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as "Alice"
And Downloading file "/drop/Alice/a.txt"
Then Downloaded content should be "abc"
Scenario: Put file same file multiple times via files drop
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/drop"
And as "user0" creating a share with
| path | drop |
| shareType | 4 |
| permissions | 4 |
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc" as "Mallory"
And Dropping file "/folder/a.txt" with "def" as "Mallory"
And Downloading file "/drop/Mallory/a.txt"
Then Downloaded content should be "abc"
And Downloading file "/drop/Mallory/a (2).txt"
Then Downloaded content should be "def"

2
dist/128-128.js vendored

File diff suppressed because one or more lines are too long

1
dist/128-128.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
128-128.js.license

2
dist/4589-4589.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";(self.webpackChunknextcloud=self.webpackChunknextcloud||[]).push([[4589],{7869:(t,e,n)=>{n.d(e,{A:()=>l});var a=n(71354),i=n.n(a),s=n(76314),o=n.n(s)()(i());o.push([t.id,".public-auth-prompt__subtitle{font-size:16px;margin-block:12px}.public-auth-prompt__header{margin-block:12px}.public-auth-prompt__form{margin-block:24px}","",{version:3,sources:["webpack://./apps/files_sharing/src/views/PublicAuthPrompt.vue"],names:[],mappings:"AAEC,8BAEC,cAAA,CACA,iBAAA,CAGD,4BAEC,iBAAA,CAGD,0BAEC,iBAAA",sourcesContent:["\n.public-auth-prompt {\n\t&__subtitle {\n\t\t// Smaller than dialog title\n\t\tfont-size: 16px;\n\t\tmargin-block: 12px;\n\t}\n\n\t&__header {\n\t\t// Fix extra margin generating an unwanted gap\n\t\tmargin-block: 12px;\n\t}\n\n\t&__form {\n\t\t// Double the margin of the header\n\t\tmargin-block: 24px;\n\t}\n}\n"],sourceRoot:""}]);const l=o},4589:(t,e,n)=>{n.r(e),n.d(e,{default:()=>v});var a=n(85471),i=n(53334),s=n(54332),o=n(94219),l=n(52201),r=n(82182),p=n(32981);const u=(0,a.pM)({name:"PublicAuthPrompt",components:{NcButton:s.A,NcDialog:o.A,NcNoteCard:l.A,NcTextField:r.A},setup:()=>({t:i.t,owner:(0,p.C)("files_sharing","owner",""),ownerDisplayName:(0,p.C)("files_sharing","ownerDisplayName",""),label:(0,p.C)("files_sharing","label",""),note:(0,p.C)("files_sharing","note",""),filename:(0,p.C)("files_sharing","filename","")}),data:()=>({name:""}),computed:{dialogName(){return this.t("files_sharing","Upload files to {folder}",{folder:this.label||this.filename})}},beforeMount(){const t=localStorage.getItem("nick");t&&(this.name=t)},methods:{onSubmit(){const t=this.$refs.form;t.checkValidity()?""!==this.name.trim()&&(localStorage.setItem("nick",this.name),this.$emit("close")):t.reportValidity()}}});var c=n(85072),m=n.n(c),h=n(97825),d=n.n(h),_=n(77659),f=n.n(_),b=n(55056),g=n.n(b),A=n(10540),C=n.n(A),y=n(41113),x=n.n(y),N=n(7869),k={};k.styleTagTransform=x(),k.setAttributes=g(),k.insert=f().bind(null,"head"),k.domAPI=d(),k.insertStyleElement=C(),m()(N.A,k),N.A&&N.A.locals&&N.A.locals;const v=(0,n(14486).A)(u,(function(){var t=this,e=t._self._c;return t._self._setupProxy,e("NcDialog",{staticClass:"public-auth-prompt",attrs:{"dialog-classes":"public-auth-prompt__dialog","can-close":!1,name:t.dialogName},scopedSlots:t._u([{key:"actions",fn:function(){return[e("NcButton",{ref:"submit",attrs:{disabled:""===t.name.trim()},on:{click:t.onSubmit}},[t._v("\n\t\t\t"+t._s(t.t("files_sharing","Submit name"))+"\n\t\t")])]},proxy:!0}])},[t.owner?e("h3",{staticClass:"public-auth-prompt__subtitle"},[t._v("\n\t\t"+t._s(t.t("files_sharing","{ownerDisplayName} shared a folder with you.",{ownerDisplayName:t.ownerDisplayName}))+"\n\t")]):t._e(),t._v(" "),e("NcNoteCard",{staticClass:"public-auth-prompt__header",attrs:{type:"info"}},[e("p",{staticClass:"public-auth-prompt__description",attrs:{id:"public-auth-prompt-dialog-description"}},[t._v("\n\t\t\t"+t._s(t.t("files_sharing","To upload files, you need to provide your name first."))+"\n\t\t")])]),t._v(" "),e("form",{ref:"form",staticClass:"public-auth-prompt__form",attrs:{"aria-describedby":"public-auth-prompt-dialog-description"},on:{submit:function(t){t.preventDefault(),t.stopPropagation()}}},[e("NcTextField",{ref:"input",staticClass:"public-auth-prompt__input",attrs:{label:t.t("files_sharing","Enter your name"),name:"name",required:!0,minlength:2,value:t.name},on:{"update:value":function(e){t.name=e}}})],1)],1)}),[],!1,null,null,null).exports}}]);
//# sourceMappingURL=4589-4589.js.map?v=f528d9600121156d9e2c

174
dist/4589-4589.js.license vendored Normal file
View file

@ -0,0 +1,174 @@
SPDX-License-Identifier: MIT
SPDX-License-Identifier: ISC
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-FileCopyrightText: inherits developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: assert developers
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Raynos <raynos2@gmail.com>
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Joyent
SPDX-FileCopyrightText: Jordan Harband <ljharb@gmail.com>
SPDX-FileCopyrightText: Jordan Harband
SPDX-FileCopyrightText: John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)
SPDX-FileCopyrightText: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
SPDX-FileCopyrightText: Guillaume Chau <guillaume.b.chau@gmail.com>
SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Andris Reinman
This file is generated from multiple sources. Included packages:
- @nextcloud/initial-state
- version: 2.2.0
- license: GPL-3.0-or-later
- @nextcloud/l10n
- version: 3.1.0
- license: GPL-3.0-or-later
- @nextcloud/router
- version: 3.0.1
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 8.14.0
- license: AGPL-3.0-or-later
- @vueuse/core
- version: 10.11.0
- license: MIT
- @vueuse/shared
- version: 10.11.0
- license: MIT
- assert
- version: 2.1.0
- license: MIT
- available-typed-arrays
- version: 1.0.7
- license: MIT
- call-bind
- version: 1.0.7
- license: MIT
- console-browserify
- version: 1.2.0
- license: MIT
- css-loader
- version: 6.10.0
- license: MIT
- define-data-property
- version: 1.1.4
- license: MIT
- define-properties
- version: 1.2.1
- license: MIT
- dompurify
- version: 3.1.4
- license: (MPL-2.0 OR Apache-2.0)
- es-define-property
- version: 1.0.0
- license: MIT
- es-errors
- version: 1.3.0
- license: MIT
- escape-html
- version: 1.0.3
- license: MIT
- floating-vue
- version: 1.0.0-beta.19
- license: MIT
- focus-trap
- version: 7.5.4
- license: MIT
- for-each
- version: 0.3.3
- license: MIT
- function-bind
- version: 1.1.2
- license: MIT
- get-intrinsic
- version: 1.2.4
- license: MIT
- gopd
- version: 1.0.1
- license: MIT
- has-property-descriptors
- version: 1.0.2
- license: MIT
- has-proto
- version: 1.0.3
- license: MIT
- has-symbols
- version: 1.0.3
- license: MIT
- has-tostringtag
- version: 1.0.2
- license: MIT
- hasown
- version: 2.0.2
- license: MIT
- inherits
- version: 2.0.4
- license: ISC
- is-arguments
- version: 1.1.1
- license: MIT
- is-callable
- version: 1.2.7
- license: MIT
- is-generator-function
- version: 1.0.10
- license: MIT
- is-nan
- version: 1.3.2
- license: MIT
- is-typed-array
- version: 1.1.13
- license: MIT
- lodash.get
- version: 4.4.2
- license: MIT
- node-gettext
- version: 3.0.0
- license: MIT
- object-is
- version: 1.1.5
- license: MIT
- object-keys
- version: 1.1.1
- license: MIT
- object.assign
- version: 4.1.5
- license: MIT
- possible-typed-array-names
- version: 1.0.0
- license: MIT
- process
- version: 0.11.10
- license: MIT
- set-function-length
- version: 1.2.1
- license: MIT
- style-loader
- version: 3.3.4
- license: MIT
- tabbable
- version: 6.2.0
- license: MIT
- util
- version: 0.12.5
- license: MIT
- vue-loader
- version: 15.11.1
- license: MIT
- vue
- version: 2.7.16
- license: MIT
- which-typed-array
- version: 1.1.15
- license: MIT
- nextcloud
- version: 1.0.0
- license: AGPL-3.0-or-later

1
dist/4589-4589.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/4589-4589.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
4589-4589.js.license

2
dist/4845-4845.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
4845-4845.js.license

2
dist/6968-6968.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/6968-6968.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/6968-6968.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
6968-6968.js.license

2
dist/8755-8755.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/8755-8755.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/8755-8755.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
8755-8755.js.license

2
dist/8971-8971.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
8971-8971.js.license

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
(()=>{"use strict";var e,r,t,i={66747:(e,r,t)=>{var i=t(61338),o=t(85168),n=t(63814),a=t(53334);const l=(0,t(35947).YK)().setApp("files").detectUser().build();document.addEventListener("DOMContentLoaded",(function(){const e=window.OCA;e.UnifiedSearch&&(l.info("Initializing unified search plugin: folder search from files app"),e.UnifiedSearch.registerFilterAction({id:"files",appId:"files",label:(0,a.Tl)("files","In folder"),icon:(0,n.d0)("files","app.svg"),callback:()=>{(0,o.a1)("Pick plain text files").addMimeTypeFilter("httpd/unix-directory").allowDirectories(!0).addButton({label:"Pick",callback:e=>{l.info("Folder picked",{folder:e[0]});const r=e[0];(0,i.Ic)("nextcloud:unified-search:add-filter",{id:"files",payload:r,filterUpdateText:(0,a.Tl)("files","Search in folder: {folder}",{folder:r.basename}),filterParams:{path:r.path}})}}).build().pick()}}))}))}},o={};function n(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return i[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=i,e=[],n.O=(r,t,i,o)=>{if(!t){var a=1/0;for(s=0;s<e.length;s++){t=e[s][0],i=e[s][1],o=e[s][2];for(var l=!0,d=0;d<t.length;d++)(!1&o||a>=o)&&Object.keys(n.O).every((e=>n.O[e](t[d])))?t.splice(d--,1):(l=!1,o<a&&(a=o));if(l){e.splice(s--,1);var c=i();void 0!==c&&(r=c)}}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,i,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,t)=>(n.f[t](e,r),r)),[])),n.u=e=>e+"-"+e+".js?v="+{4254:"5c2324570f66dff0c8a1",9480:"f3ebcf41e93bbd8cd678"}[e],n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",n.l=(e,i,o,a)=>{if(r[e])r[e].push(i);else{var l,d;if(void 0!==o)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var u=c[s];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==t+o){l=u;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,n.nc&&l.setAttribute("nonce",n.nc),l.setAttribute("data-webpack",t+o),l.src=e),r[e]=[i];var f=(t,i)=>{l.onerror=l.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(i))),t)return t(i)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=f.bind(null,l.onerror),l.onload=f.bind(null,l.onload),d&&document.head.appendChild(l)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.j=2277,(()=>{var e;n.g.importScripts&&(e=n.g.location+"");var r=n.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var i=t.length-1;i>-1&&(!e||!/^http(s?):/.test(e));)e=t[i--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),n.p=e})(),(()=>{n.b=document.baseURI||self.location.href;var e={2277:0};n.f.j=(r,t)=>{var i=n.o(e,r)?e[r]:void 0;if(0!==i)if(i)t.push(i[2]);else{var o=new Promise(((t,o)=>i=e[r]=[t,o]));t.push(i[2]=o);var a=n.p+n.u(r),l=new Error;n.l(a,(t=>{if(n.o(e,r)&&(0!==(i=e[r])&&(e[r]=void 0),i)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",l.name="ChunkLoadError",l.type=o,l.request=a,i[1](l)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,t)=>{var i,o,a=t[0],l=t[1],d=t[2],c=0;if(a.some((r=>0!==e[r]))){for(i in l)n.o(l,i)&&(n.m[i]=l[i]);if(d)var s=d(n)}for(r&&r(t);c<a.length;c++)o=a[c],n.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return n.O(s)},t=self.webpackChunknextcloud=self.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),n.nc=void 0;var a=n.O(void 0,[4208],(()=>n(66747)));a=n.O(a)})();
//# sourceMappingURL=files-search.js.map?v=cfd84c76c0213e1c45a5
(()=>{"use strict";var e,r,t,i={66747:(e,r,t)=>{var i=t(61338),o=t(85168),n=t(63814),a=t(53334);const l=(0,t(35947).YK)().setApp("files").detectUser().build();document.addEventListener("DOMContentLoaded",(function(){const e=window.OCA;e.UnifiedSearch&&(l.info("Initializing unified search plugin: folder search from files app"),e.UnifiedSearch.registerFilterAction({id:"files",appId:"files",label:(0,a.Tl)("files","In folder"),icon:(0,n.d0)("files","app.svg"),callback:()=>{(0,o.a1)("Pick plain text files").addMimeTypeFilter("httpd/unix-directory").allowDirectories(!0).addButton({label:"Pick",callback:e=>{l.info("Folder picked",{folder:e[0]});const r=e[0];(0,i.Ic)("nextcloud:unified-search:add-filter",{id:"files",payload:r,filterUpdateText:(0,a.Tl)("files","Search in folder: {folder}",{folder:r.basename}),filterParams:{path:r.path}})}}).build().pick()}}))}))}},o={};function n(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return i[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=i,e=[],n.O=(r,t,i,o)=>{if(!t){var a=1/0;for(s=0;s<e.length;s++){t=e[s][0],i=e[s][1],o=e[s][2];for(var l=!0,d=0;d<t.length;d++)(!1&o||a>=o)&&Object.keys(n.O).every((e=>n.O[e](t[d])))?t.splice(d--,1):(l=!1,o<a&&(a=o));if(l){e.splice(s--,1);var c=i();void 0!==c&&(r=c)}}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,i,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,t)=>(n.f[t](e,r),r)),[])),n.u=e=>e+"-"+e+".js?v="+{4254:"5c2324570f66dff0c8a1",9480:"f3ebcf41e93bbd8cd678"}[e],n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",n.l=(e,i,o,a)=>{if(r[e])r[e].push(i);else{var l,d;if(void 0!==o)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var f=c[s];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==t+o){l=f;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,n.nc&&l.setAttribute("nonce",n.nc),l.setAttribute("data-webpack",t+o),l.src=e),r[e]=[i];var u=(t,i)=>{l.onerror=l.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(i))),t)return t(i)},p=setTimeout(u.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=u.bind(null,l.onerror),l.onload=u.bind(null,l.onload),d&&document.head.appendChild(l)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.j=2277,(()=>{var e;n.g.importScripts&&(e=n.g.location+"");var r=n.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var i=t.length-1;i>-1&&(!e||!/^http(s?):/.test(e));)e=t[i--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),n.p=e})(),(()=>{n.b=document.baseURI||self.location.href;var e={2277:0};n.f.j=(r,t)=>{var i=n.o(e,r)?e[r]:void 0;if(0!==i)if(i)t.push(i[2]);else{var o=new Promise(((t,o)=>i=e[r]=[t,o]));t.push(i[2]=o);var a=n.p+n.u(r),l=new Error;n.l(a,(t=>{if(n.o(e,r)&&(0!==(i=e[r])&&(e[r]=void 0),i)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",l.name="ChunkLoadError",l.type=o,l.request=a,i[1](l)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,t)=>{var i,o,a=t[0],l=t[1],d=t[2],c=0;if(a.some((r=>0!==e[r]))){for(i in l)n.o(l,i)&&(n.m[i]=l[i]);if(d)var s=d(n)}for(r&&r(t);c<a.length;c++)o=a[c],n.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return n.O(s)},t=self.webpackChunknextcloud=self.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),n.nc=void 0;var a=n.O(void 0,[4208],(()=>n(66747)));a=n.O(a)})();
//# sourceMappingURL=files-search.js.map?v=5a3591bc322b26e3d654

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

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

@ -11,6 +11,7 @@ SPDX-FileCopyrightText: assert developers
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Roeland Jago Douma
SPDX-FileCopyrightText: Raynos <raynos2@gmail.com>
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Matt Zabriskie
@ -39,6 +40,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/axios
- version: 2.5.0
- license: GPL-3.0-or-later
- @nextcloud/capabilities
- version: 1.2.0
- license: GPL-3.0-or-later
- semver
- version: 7.6.2
- license: ISC

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

@ -0,0 +1,2 @@
(()=>{"use strict";var e,t,r,o={38943:(e,t,r)=>{var o=r(85168),n=r(85471);const a=(0,r(35947).YK)().setApp("files_sharing").detectUser().build(),i=localStorage.getItem("nick"),l=localStorage.getItem("publicAuthPromptShown");i&&l?a.debug("Public auth prompt already shown. Current nickname is '".concat(i,"'")):(0,o.Ss)((0,n.$V)((()=>Promise.all([r.e(4208),r.e(4589)]).then(r.bind(r,4589)))),{},(()=>localStorage.setItem("publicAuthPromptShown","true")))}},n={};function a(e){var t=n[e];if(void 0!==t)return t.exports;var r=n[e]={id:e,loaded:!1,exports:{}};return o[e].call(r.exports,r,r.exports,a),r.loaded=!0,r.exports}a.m=o,e=[],a.O=(t,r,o,n)=>{if(!r){var i=1/0;for(s=0;s<e.length;s++){r=e[s][0],o=e[s][1],n=e[s][2];for(var l=!0,c=0;c<r.length;c++)(!1&n||i>=n)&&Object.keys(a.O).every((e=>a.O[e](r[c])))?r.splice(c--,1):(l=!1,n<i&&(i=n));if(l){e.splice(s--,1);var u=o();void 0!==u&&(t=u)}}return t}n=n||0;for(var s=e.length;s>0&&e[s-1][2]>n;s--)e[s]=e[s-1];e[s]=[r,o,n]},a.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var r in t)a.o(t,r)&&!a.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((t,r)=>(a.f[r](e,t),t)),[])),a.u=e=>e+"-"+e+".js?v="+{4254:"5c2324570f66dff0c8a1",4589:"f528d9600121156d9e2c",9480:"f3ebcf41e93bbd8cd678"}[e],a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),t={},r="nextcloud:",a.l=(e,o,n,i)=>{if(t[e])t[e].push(o);else{var l,c;if(void 0!==n)for(var u=document.getElementsByTagName("script"),s=0;s<u.length;s++){var d=u[s];if(d.getAttribute("src")==e||d.getAttribute("data-webpack")==r+n){l=d;break}}l||(c=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",r+n),l.src=e),t[e]=[o];var p=(r,o)=>{l.onerror=l.onload=null,clearTimeout(f);var n=t[e];if(delete t[e],l.parentNode&&l.parentNode.removeChild(l),n&&n.forEach((e=>e(o))),r)return r(o)},f=setTimeout(p.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=p.bind(null,l.onerror),l.onload=p.bind(null,l.onload),c&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=9804,(()=>{var e;a.g.importScripts&&(e=a.g.location+"");var t=a.g.document;if(!e&&t&&(t.currentScript&&(e=t.currentScript.src),!e)){var r=t.getElementsByTagName("script");if(r.length)for(var o=r.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=r[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b=document.baseURI||self.location.href;var e={9804:0};a.f.j=(t,r)=>{var o=a.o(e,t)?e[t]:void 0;if(0!==o)if(o)r.push(o[2]);else{var n=new Promise(((r,n)=>o=e[t]=[r,n]));r.push(o[2]=n);var i=a.p+a.u(t),l=new Error;a.l(i,(r=>{if(a.o(e,t)&&(0!==(o=e[t])&&(e[t]=void 0),o)){var n=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;l.message="Loading chunk "+t+" failed.\n("+n+": "+i+")",l.name="ChunkLoadError",l.type=n,l.request=i,o[1](l)}}),"chunk-"+t,t)}},a.O.j=t=>0===e[t];var t=(t,r)=>{var o,n,i=r[0],l=r[1],c=r[2],u=0;if(i.some((t=>0!==e[t]))){for(o in l)a.o(l,o)&&(a.m[o]=l[o]);if(c)var s=c(a)}for(t&&t(r);u<i.length;u++)n=i[u],a.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return a.O(s)},r=self.webpackChunknextcloud=self.webpackChunknextcloud||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})(),a.nc=void 0;var i=a.O(void 0,[4208],(()=>a(38943)));i=a.O(i)})();
//# sourceMappingURL=files_sharing-public-file-request.js.map?v=4ee42b64953b65a29438

View file

@ -1,7 +1,6 @@
SPDX-License-Identifier: MIT
SPDX-License-Identifier: ISC
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-FileCopyrightText: inherits developers
@ -9,12 +8,9 @@ SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: assert developers
SPDX-FileCopyrightText: Varun A P
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Roeland Jago Douma
SPDX-FileCopyrightText: Raynos <raynos2@gmail.com>
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: Joyent
SPDX-FileCopyrightText: Jordan Harband <ljharb@gmail.com>
SPDX-FileCopyrightText: Jordan Harband
@ -22,7 +18,6 @@ SPDX-FileCopyrightText: John-David Dalton <john.david.dalton@gmail.com> (http://
SPDX-FileCopyrightText: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
SPDX-FileCopyrightText: Guillaume Chau <guillaume.b.chau@gmail.com>
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: David Clark
@ -36,12 +31,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/auth
- version: 2.3.0
- license: GPL-3.0-or-later
- @nextcloud/axios
- version: 2.5.0
- license: GPL-3.0-or-later
- @nextcloud/capabilities
- version: 1.2.0
- license: GPL-3.0-or-later
- @nextcloud/dialogs
- version: 5.3.5
- license: AGPL-3.0-or-later
@ -51,12 +40,12 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/event-bus
- version: 3.3.1
- license: GPL-3.0-or-later
- @nextcloud/initial-state
- version: 2.2.0
- license: GPL-3.0-or-later
- @nextcloud/l10n
- version: 3.1.0
- license: GPL-3.0-or-later
- @nextcloud/logger
- version: 3.0.2
- license: GPL-3.0-or-later
- @nextcloud/router
- version: 3.0.1
- license: GPL-3.0-or-later
@ -75,15 +64,6 @@ This file is generated from multiple sources. Included packages:
- available-typed-arrays
- version: 1.0.7
- license: MIT
- axios
- version: 1.7.2
- license: MIT
- base64-js
- version: 1.5.1
- license: MIT
- buffer
- version: 6.0.3
- license: MIT
- call-bind
- version: 1.0.7
- license: MIT
@ -144,9 +124,6 @@ This file is generated from multiple sources. Included packages:
- hasown
- version: 2.0.2
- license: MIT
- ieee754
- version: 1.2.1
- license: BSD-3-Clause
- inherits
- version: 2.0.4
- license: ISC
@ -213,6 +190,9 @@ This file is generated from multiple sources. Included packages:
- vue
- version: 2.7.16
- license: MIT
- webpack
- version: 5.93.0
- license: MIT
- which-typed-array
- version: 1.1.15
- license: MIT

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
files_sharing-public-file-request.js.license

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

File diff suppressed because one or more lines are too long

View file

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