diff options
Diffstat (limited to 'apps')
48 files changed, 1033 insertions, 321 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index b37e73fa5b6..9140c674716 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ <name>WebDAV</name> <summary>WebDAV endpoint</summary> <description>WebDAV endpoint</description> - <version>1.26.0</version> + <version>1.27.0</version> <licence>agpl</licence> <author>owncloud.org</author> <namespace>DAV</namespace> diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 03495efc1bd..f5c44579ed9 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -293,6 +293,7 @@ return array( 'OCA\\DAV\\Migration\\Version1017Date20210216083742' => $baseDir . '/../lib/Migration/Version1017Date20210216083742.php', 'OCA\\DAV\\Migration\\Version1018Date20210312100735' => $baseDir . '/../lib/Migration/Version1018Date20210312100735.php', 'OCA\\DAV\\Migration\\Version1024Date20211221144219' => $baseDir . '/../lib/Migration/Version1024Date20211221144219.php', + 'OCA\\DAV\\Migration\\Version1027Date20230504122946' => $baseDir . '/../lib/Migration/Version1027Date20230504122946.php', 'OCA\\DAV\\Profiler\\ProfilerPlugin' => $baseDir . '/../lib/Profiler/ProfilerPlugin.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index a0b742b35b8..ea7da582b3e 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -308,6 +308,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1017Date20210216083742' => __DIR__ . '/..' . '/../lib/Migration/Version1017Date20210216083742.php', 'OCA\\DAV\\Migration\\Version1018Date20210312100735' => __DIR__ . '/..' . '/../lib/Migration/Version1018Date20210312100735.php', 'OCA\\DAV\\Migration\\Version1024Date20211221144219' => __DIR__ . '/..' . '/../lib/Migration/Version1024Date20211221144219.php', + 'OCA\\DAV\\Migration\\Version1027Date20230504122946' => __DIR__ . '/..' . '/../lib/Migration/Version1027Date20230504122946.php', 'OCA\\DAV\\Profiler\\ProfilerPlugin' => __DIR__ . '/..' . '/../lib/Profiler/ProfilerPlugin.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php', 'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php', diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 329197445dd..e164e420ae3 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -299,14 +299,12 @@ class IMipPlugin extends SabreIMipPlugin { $message->useTemplate($template); - $vCalendar = $this->imipService->generateVCalendar($iTipMessage, $vEvent); - - $attachment = $this->mailer->createAttachment( - $vCalendar->serialize(), + $itip_msg = $iTipMessage->message->serialize(); + $message->attachInline( + $itip_msg, 'event.ics', - 'text/calendar; method=' . $iTipMessage->method + 'text/calendar; method=' . $iTipMessage->method, ); - $message->attach($attachment); try { $failed = $this->mailer->send($message); diff --git a/apps/dav/lib/CardDAV/Converter.php b/apps/dav/lib/CardDAV/Converter.php index 409fce62105..e35bc41abd2 100644 --- a/apps/dav/lib/CardDAV/Converter.php +++ b/apps/dav/lib/CardDAV/Converter.php @@ -29,15 +29,12 @@ namespace OCA\DAV\CardDAV; use Exception; use OCP\Accounts\IAccountManager; -use OCP\Accounts\PropertyDoesNotExistException; use OCP\IImage; use OCP\IUser; use Sabre\VObject\Component\VCard; use Sabre\VObject\Property\Text; -use function array_merge; class Converter { - /** @var IAccountManager */ private $accountManager; @@ -46,13 +43,7 @@ class Converter { } public function createCardFromUser(IUser $user): ?VCard { - $account = $this->accountManager->getAccount($user); - $userProperties = $account->getProperties(); - try { - $additionalEmailsCollection = $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL); - $userProperties = array_merge($userProperties, $additionalEmailsCollection->getProperties()); - } catch (PropertyDoesNotExistException $e) { - } + $userProperties = $this->accountManager->getAccount($user)->getAllProperties(); $uid = $user->getUID(); $cloudId = $user->getCloudId(); @@ -65,47 +56,49 @@ class Converter { $publish = false; foreach ($userProperties as $property) { - $shareWithTrustedServers = - $property->getScope() === IAccountManager::SCOPE_FEDERATED || - $property->getScope() === IAccountManager::SCOPE_PUBLISHED; - - $emptyValue = $property->getValue() === ''; - - if ($shareWithTrustedServers && !$emptyValue) { - $publish = true; - switch ($property->getName()) { - case IAccountManager::PROPERTY_DISPLAYNAME: - $vCard->add(new Text($vCard, 'FN', $property->getValue())); - $vCard->add(new Text($vCard, 'N', $this->splitFullName($property->getValue()))); - break; - case IAccountManager::PROPERTY_AVATAR: - if ($image !== null) { - $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]); - } - break; - case IAccountManager::COLLECTION_EMAIL: - case IAccountManager::PROPERTY_EMAIL: - $vCard->add(new Text($vCard, 'EMAIL', $property->getValue(), ['TYPE' => 'OTHER'])); - break; - case IAccountManager::PROPERTY_WEBSITE: - $vCard->add(new Text($vCard, 'URL', $property->getValue())); - break; - case IAccountManager::PROPERTY_PHONE: - $vCard->add(new Text($vCard, 'TEL', $property->getValue(), ['TYPE' => 'OTHER'])); - break; - case IAccountManager::PROPERTY_ADDRESS: - $vCard->add(new Text($vCard, 'ADR', $property->getValue(), ['TYPE' => 'OTHER'])); - break; - case IAccountManager::PROPERTY_TWITTER: - $vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $property->getValue(), ['TYPE' => 'TWITTER'])); - break; - case IAccountManager::PROPERTY_ORGANISATION: - $vCard->add(new Text($vCard, 'ORG', $property->getValue())); - break; - case IAccountManager::PROPERTY_ROLE: - $vCard->add(new Text($vCard, 'TITLE', $property->getValue())); - break; - } + if (empty($property->getValue())) { + continue; + } + + $scope = $property->getScope(); + // Do not write private data to the system address book at all + if ($scope === IAccountManager::SCOPE_PRIVATE || empty($scope)) { + continue; + } + + $publish = true; + switch ($property->getName()) { + case IAccountManager::PROPERTY_DISPLAYNAME: + $vCard->add(new Text($vCard, 'FN', $property->getValue(), ['X-NC-SCOPE' => $scope])); + $vCard->add(new Text($vCard, 'N', $this->splitFullName($property->getValue()), ['X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_AVATAR: + if ($image !== null) { + $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType(), ['X-NC-SCOPE' => $scope]]); + } + break; + case IAccountManager::COLLECTION_EMAIL: + case IAccountManager::PROPERTY_EMAIL: + $vCard->add(new Text($vCard, 'EMAIL', $property->getValue(), ['TYPE' => 'OTHER', 'X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_WEBSITE: + $vCard->add(new Text($vCard, 'URL', $property->getValue(), ['X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_PHONE: + $vCard->add(new Text($vCard, 'TEL', $property->getValue(), ['TYPE' => 'OTHER', 'X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_ADDRESS: + $vCard->add(new Text($vCard, 'ADR', $property->getValue(), ['TYPE' => 'OTHER', 'X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_TWITTER: + $vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $property->getValue(), ['TYPE' => 'TWITTER', 'X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_ORGANISATION: + $vCard->add(new Text($vCard, 'ORG', $property->getValue(), ['X-NC-SCOPE' => $scope])); + break; + case IAccountManager::PROPERTY_ROLE: + $vCard->add(new Text($vCard, 'TITLE', $property->getValue(), ['X-NC-SCOPE' => $scope])); + break; } } diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index 502e353acb3..a803a1e6b24 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -27,20 +27,34 @@ declare(strict_types=1); */ namespace OCA\DAV\CardDAV; +use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException; +use OCA\Federation\TrustedServers; +use OCP\Accounts\IAccountManager; use OCP\IConfig; use OCP\IL10N; +use OCP\IRequest; +use Sabre\CardDAV\Backend\SyncSupport; use Sabre\CardDAV\Backend\BackendInterface; +use Sabre\CardDAV\Card; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\Reader; class SystemAddressbook extends AddressBook { /** @var IConfig */ private $config; + private ?TrustedServers $trustedServers; + private ?IRequest $request; - public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n, IConfig $config) { + public function __construct(BackendInterface $carddavBackend, array $addressBookInfo, IL10N $l10n, IConfig $config, ?IRequest $request = null, ?TrustedServers $trustedServers = null) { parent::__construct($carddavBackend, $addressBookInfo, $l10n); $this->config = $config; + $this->request = $request; + $this->trustedServers = $trustedServers; } - public function getChildren() { + public function getChildren(): array { $shareEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; $shareEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; $shareEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; @@ -50,4 +64,165 @@ class SystemAddressbook extends AddressBook { return parent::getChildren(); } + + /** + * @param array $paths + * @return Card[] + * @throws NotFound + */ + public function getMultipleChildren($paths): array { + if (!$this->isFederation()) { + return parent::getMultipleChildren($paths); + } + + $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths); + $children = []; + /** @var array $obj */ + foreach ($objs as $obj) { + if (empty($obj)) { + continue; + } + $carddata = $this->extractCarddata($obj); + if (empty($carddata)) { + continue; + } else { + $obj['carddata'] = $carddata; + } + $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + return $children; + } + + /** + * @param string $name + * @return Card + * @throws NotFound + * @throws Forbidden + */ + public function getChild($name): Card { + if (!$this->isFederation()) { + return parent::getChild($name); + } + + $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name); + if (!$obj) { + throw new NotFound('Card not found'); + } + $carddata = $this->extractCarddata($obj); + if (empty($carddata)) { + throw new Forbidden(); + } else { + $obj['carddata'] = $carddata; + } + return new Card($this->carddavBackend, $this->addressBookInfo, $obj); + } + + /** + * @throws UnsupportedLimitOnInitialSyncException + */ + public function getChanges($syncToken, $syncLevel, $limit = null) { + if (!$syncToken && $limit) { + throw new UnsupportedLimitOnInitialSyncException(); + } + + if (!$this->carddavBackend instanceof SyncSupport) { + return null; + } + + if (!$this->isFederation()) { + return parent::getChanges($syncToken, $syncLevel, $limit); + } + + $changed = $this->carddavBackend->getChangesForAddressBook( + $this->addressBookInfo['id'], + $syncToken, + $syncLevel, + $limit + ); + + if (empty($changed)) { + return $changed; + } + + $added = $modified = $deleted = []; + foreach ($changed['added'] as $uri) { + try { + $this->getChild($uri); + $added[] = $uri; + } catch (NotFound | Forbidden $e) { + $deleted[] = $uri; + } + } + foreach ($changed['modified'] as $uri) { + try { + $this->getChild($uri); + $modified[] = $uri; + } catch (NotFound | Forbidden $e) { + $deleted[] = $uri; + } + } + $changed['added'] = $added; + $changed['modified'] = $modified; + $changed['deleted'] = $deleted; + return $changed; + } + + private function isFederation(): bool { + if ($this->trustedServers === null || $this->request === null) { + return false; + } + + /** @psalm-suppress NoInterfaceProperties */ + if ($this->request->server['PHP_AUTH_USER'] !== 'system') { + return false; + } + + /** @psalm-suppress NoInterfaceProperties */ + $sharedSecret = $this->request->server['PHP_AUTH_PW']; + if ($sharedSecret === null) { + return false; + } + + $servers = $this->trustedServers->getServers(); + $trusted = array_filter($servers, function ($trustedServer) use ($sharedSecret) { + return $trustedServer['shared_secret'] === $sharedSecret; + }); + // Authentication is fine, but it's not for a federated share + if (empty($trusted)) { + return false; + } + + return true; + } + + /** + * If the validation doesn't work the card is "not found" so we + * return empty carddata even if the carddata might exist in the local backend. + * This can happen when a user sets the required properties + * FN, N to a local scope only but the request is from + * a federated share. + * + * @see https://github.com/nextcloud/server/issues/38042 + * + * @param array $obj + * @return string|null + */ + private function extractCarddata(array $obj): ?string { + $obj['acl'] = $this->getChildACL(); + $cardData = $obj['carddata']; + /** @var VCard $vCard */ + $vCard = Reader::read($cardData); + foreach ($vCard->children() as $child) { + $scope = $child->offsetGet('X-NC-SCOPE'); + if ($scope !== null && $scope->getValue() === IAccountManager::SCOPE_LOCAL) { + $vCard->remove($child); + } + } + $messages = $vCard->validate(); + if (!empty($messages)) { + return null; + } + + return $vCard->serialize(); + } } diff --git a/apps/dav/lib/CardDAV/UserAddressBooks.php b/apps/dav/lib/CardDAV/UserAddressBooks.php index 98957301120..85795604f28 100644 --- a/apps/dav/lib/CardDAV/UserAddressBooks.php +++ b/apps/dav/lib/CardDAV/UserAddressBooks.php @@ -30,8 +30,13 @@ namespace OCA\DAV\CardDAV; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CardDAV\Integration\IAddressBookProvider; use OCA\DAV\CardDAV\Integration\ExternalAddressBook; +use OCA\Federation\TrustedServers; +use OCP\AppFramework\QueryException; use OCP\IConfig; use OCP\IL10N; +use OCP\IRequest; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; use Sabre\CardDAV\Backend; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\CardDAV\IAddressBook; @@ -73,7 +78,15 @@ class UserAddressBooks extends \Sabre\CardDAV\AddressBookHome { /** @var IAddressBook[] $objects */ $objects = array_map(function (array $addressBook) { if ($addressBook['principaluri'] === 'principals/system/system') { - return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config); + $trustedServers = null; + $request = null; + try { + $trustedServers = \OC::$server->get(TrustedServers::class); + $request = \OC::$server->get(IRequest::class); + } catch (NotFoundExceptionInterface | ContainerExceptionInterface $e) { + // nothing to do, the request / trusted servers don't exist + } + return new SystemAddressbook($this->carddavBackend, $addressBook, $this->l10n, $this->config, $request, $trustedServers); } return new AddressBook($this->carddavBackend, $addressBook, $this->l10n); diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index b0f17417d21..a7cafeb4a5e 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -422,14 +422,15 @@ class File extends Node implements IFile { } } - /** - * @param string $path - */ - private function emitPreHooks($exists, $path = null) { + private function emitPreHooks(bool $exists, ?string $path = null): bool { if (is_null($path)) { $path = $this->path; } $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); + if ($hookPath === null) { + // We only trigger hooks from inside default view + return true; + } $run = true; if (!$exists) { @@ -450,14 +451,15 @@ class File extends Node implements IFile { return $run; } - /** - * @param string $path - */ - private function emitPostHooks($exists, $path = null) { + private function emitPostHooks(bool $exists, ?string $path = null): void { if (is_null($path)) { $path = $this->path; } $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); + if ($hookPath === null) { + // We only trigger hooks from inside default view + return; + } if (!$exists) { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [ \OC\Files\Filesystem::signal_param_path => $hookPath diff --git a/apps/dav/lib/Migration/Version1027Date20230504122946.php b/apps/dav/lib/Migration/Version1027Date20230504122946.php new file mode 100644 index 00000000000..e9ae174f56e --- /dev/null +++ b/apps/dav/lib/Migration/Version1027Date20230504122946.php @@ -0,0 +1,54 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Anna Larch <anna.larch@gmx.net> + * + * @author Anna Larch <anna.larch@gmx.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\DAV\Migration; + +use Closure; +use OCA\DAV\CardDAV\SyncService; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Psr\Log\LoggerInterface; + +class Version1027Date20230504122946 extends SimpleMigrationStep { + private SyncService $syncService; + private LoggerInterface $logger; + + public function __construct(SyncService $syncService, LoggerInterface $logger) { + $this->syncService = $syncService; + $this->logger = $logger; + } + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + $this->syncService->syncInstance(); + } +} diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index 224a7de77eb..c5d200d578e 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -34,6 +34,7 @@ use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagAlreadyExistsException; +use OCP\Util; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Conflict; use Sabre\DAV\Exception\Forbidden; @@ -323,8 +324,11 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { if ($user === null) { return; } - + $tags = $this->getTagsForFile($node->getId(), $user); + usort($tags, function (ISystemTag $tagA, ISystemTag $tagB): int { + return Util::naturalSortCompare($tagA->getName(), $tagB->getName()); + }); return new SystemTagList($tags, $this->tagManager, $user); }); } diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index cb7c802125c..6b660fb7c6f 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -255,17 +255,15 @@ class ChunkingV2Plugin extends ServerPlugin { public function beforeDelete(RequestInterface $request, ResponseInterface $response) { try { - $this->prepareUpload($request->getPath()); - if (!$this->uploadFolder instanceof UploadFolder) { - return true; - } - - [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath); - $storage->cancelChunkedWrite($storagePath, $this->uploadId); - return true; - } catch (NotFound $e) { + $this->prepareUpload(dirname($request->getPath())); + $this->checkPrerequisites(); + } catch (StorageInvalidException|BadRequest|NotFound $e) { return true; } + + [$storage, $storagePath] = $this->getUploadStorage($this->uploadPath); + $storage->cancelChunkedWrite($storagePath, $this->uploadId); + return true; } /** diff --git a/apps/dav/lib/UserMigration/CalendarMigrator.php b/apps/dav/lib/UserMigration/CalendarMigrator.php index 057f7dce77d..e5b404e785f 100644 --- a/apps/dav/lib/UserMigration/CalendarMigrator.php +++ b/apps/dav/lib/UserMigration/CalendarMigrator.php @@ -211,7 +211,7 @@ class CalendarMigrator implements IMigrator, ISizeEstimationMigrator { /** * {@inheritDoc} */ - public function getEstimatedExportSize(IUser $user): int { + public function getEstimatedExportSize(IUser $user): int|float { $calendarExports = $this->getCalendarExports($user, new NullOutput()); $calendarCount = count($calendarExports); @@ -230,7 +230,7 @@ class CalendarMigrator implements IMigrator, ISizeEstimationMigrator { // 450B for each component (events, todos, alarms, etc.) $size += ($componentCount * 450) / 1024; - return (int)ceil($size); + return ceil($size); } /** diff --git a/apps/dav/lib/UserMigration/ContactsMigrator.php b/apps/dav/lib/UserMigration/ContactsMigrator.php index 196d0a6110a..58e267ab28c 100644 --- a/apps/dav/lib/UserMigration/ContactsMigrator.php +++ b/apps/dav/lib/UserMigration/ContactsMigrator.php @@ -202,7 +202,7 @@ class ContactsMigrator implements IMigrator, ISizeEstimationMigrator { /** * {@inheritDoc} */ - public function getEstimatedExportSize(IUser $user): int { + public function getEstimatedExportSize(IUser $user): int|float { $addressBookExports = $this->getAddressBookExports($user, new NullOutput()); $addressBookCount = count($addressBookExports); @@ -217,7 +217,7 @@ class ContactsMigrator implements IMigrator, ISizeEstimationMigrator { // 350B for each contact $size += ($contactsCount * 350) / 1024; - return (int)ceil($size); + return ceil($size); } /** diff --git a/apps/dav/tests/unit/CardDAV/ConverterTest.php b/apps/dav/tests/unit/CardDAV/ConverterTest.php index fe45e4e5430..f4ef0332c6e 100644 --- a/apps/dav/tests/unit/CardDAV/ConverterTest.php +++ b/apps/dav/tests/unit/CardDAV/ConverterTest.php @@ -70,17 +70,15 @@ class ConverterTest extends TestCase { public function getAccountManager(IUser $user) { $account = $this->createMock(IAccount::class); $account->expects($this->any()) - ->method('getProperties') + ->method('getAllProperties') ->willReturnCallback(function () use ($user) { - return [ - $this->getAccountPropertyMock(IAccountManager::PROPERTY_DISPLAYNAME, $user->getDisplayName(), IAccountManager::SCOPE_FEDERATED), - $this->getAccountPropertyMock(IAccountManager::PROPERTY_ADDRESS, '', IAccountManager::SCOPE_LOCAL), - $this->getAccountPropertyMock(IAccountManager::PROPERTY_WEBSITE, '', IAccountManager::SCOPE_LOCAL), - $this->getAccountPropertyMock(IAccountManager::PROPERTY_EMAIL, $user->getEMailAddress(), IAccountManager::SCOPE_FEDERATED), - $this->getAccountPropertyMock(IAccountManager::PROPERTY_AVATAR, $user->getAvatarImage(-1)->data(), IAccountManager::SCOPE_FEDERATED), - $this->getAccountPropertyMock(IAccountManager::PROPERTY_PHONE, '', IAccountManager::SCOPE_LOCAL), - $this->getAccountPropertyMock(IAccountManager::PROPERTY_TWITTER, '', IAccountManager::SCOPE_LOCAL), - ]; + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_DISPLAYNAME, $user->getDisplayName(), IAccountManager::SCOPE_FEDERATED); + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_ADDRESS, '', IAccountManager::SCOPE_LOCAL); + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_WEBSITE, '', IAccountManager::SCOPE_LOCAL); + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_EMAIL, $user->getEMailAddress(), IAccountManager::SCOPE_FEDERATED); + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_AVATAR, $user->getAvatarImage(-1)->data(), IAccountManager::SCOPE_FEDERATED); + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_PHONE, '', IAccountManager::SCOPE_LOCAL); + yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_TWITTER, '', IAccountManager::SCOPE_LOCAL); }); $accountManager = $this->getMockBuilder(IAccountManager::class) diff --git a/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php b/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php new file mode 100644 index 00000000000..73393d75615 --- /dev/null +++ b/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php @@ -0,0 +1,123 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCA\DAV\Tests\unit\CardDAV; + +use OC\AppFramework\Http\Request; +use OCA\DAV\CardDAV\SystemAddressbook; +use OCA\Federation\TrustedServers; +use OCP\Accounts\IAccountManager; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\CardDAV\Backend\BackendInterface; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\Reader; +use Test\TestCase; + +class SystemAddressBookTest extends TestCase { + + private MockObject|BackendInterface $cardDavBackend; + private array $addressBookInfo; + private IL10N|MockObject $l10n; + private IConfig|MockObject $config; + private IRequest|MockObject $request; + private array $server; + private TrustedServers|MockObject $trustedServers; + private SystemAddressbook $addressBook; + + protected function setUp(): void { + parent::setUp(); + + $this->cardDavBackend = $this->createMock(BackendInterface::class); + $this->addressBookInfo = [ + 'id' => 123, + '{DAV:}displayname' => 'Accounts', + 'principaluri' => 'principals/system/system', + ]; + $this->l10n = $this->createMock(IL10N::class); + $this->config = $this->createMock(IConfig::class); + $this->request = $this->createMock(Request::class); + $this->server = [ + 'PHP_AUTH_USER' => 'system', + 'PHP_AUTH_PW' => 'shared123', + ]; + $this->request->method('__get')->with('server')->willReturn($this->server); + $this->trustedServers = $this->createMock(TrustedServers::class); + + $this->addressBook = new SystemAddressbook( + $this->cardDavBackend, + $this->addressBookInfo, + $this->l10n, + $this->config, + $this->request, + $this->trustedServers, + ); + } + + public function testGetFilteredChildForFederation(): void { + $this->trustedServers->expects(self::once()) + ->method('getServers') + ->willReturn([ + [ + 'shared_secret' => 'shared123', + ], + ]); + $vcfWithScopes = <<<VCF +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 4.4.2//EN +UID:admin +FN;X-NC-SCOPE=v2-federated:admin +N;X-NC-SCOPE=v2-federated:admin;;;; +ADR;TYPE=OTHER;X-NC-SCOPE=v2-local:Testing test test test;;;;;; +EMAIL;TYPE=OTHER;X-NC-SCOPE=v2-federated:miau_lalala@gmx.net +TEL;TYPE=OTHER;X-NC-SCOPE=v2-local:+435454454544 +CLOUD:admin@http://localhost +END:VCARD +VCF; + $originalCard = [ + 'carddata' => $vcfWithScopes, + ]; + $this->cardDavBackend->expects(self::once()) + ->method('getCard') + ->with(123, 'user.vcf') + ->willReturn($originalCard); + + $card = $this->addressBook->getChild("user.vcf"); + + /** @var VCard $vCard */ + $vCard = Reader::read($card->get()); + foreach ($vCard->children() as $child) { + $scope = $child->offsetGet('X-NC-SCOPE'); + if ($scope !== null) { + self::assertNotEquals(IAccountManager::SCOPE_PRIVATE, $scope->getValue()); + self::assertNotEquals(IAccountManager::SCOPE_LOCAL, $scope->getValue()); + } + } + } + +} diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index 985a26f6611..5d9e630704d 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -35,6 +35,9 @@ <command>OCA\Files\Command\TransferOwnership</command> <command>OCA\Files\Command\ScanAppData</command> <command>OCA\Files\Command\RepairTree</command> + <command>OCA\Files\Command\Get</command> + <command>OCA\Files\Command\Put</command> + <command>OCA\Files\Command\Delete</command> </commands> <activity> diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index 868014ecfe7..2f99d4a88de 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -27,7 +27,10 @@ return array( 'OCA\\Files\\Capabilities' => $baseDir . '/../lib/Capabilities.php', 'OCA\\Files\\Collaboration\\Resources\\Listener' => $baseDir . '/../lib/Collaboration/Resources/Listener.php', 'OCA\\Files\\Collaboration\\Resources\\ResourceProvider' => $baseDir . '/../lib/Collaboration/Resources/ResourceProvider.php', + 'OCA\\Files\\Command\\Delete' => $baseDir . '/../lib/Command/Delete.php', 'OCA\\Files\\Command\\DeleteOrphanedFiles' => $baseDir . '/../lib/Command/DeleteOrphanedFiles.php', + 'OCA\\Files\\Command\\Get' => $baseDir . '/../lib/Command/Get.php', + 'OCA\\Files\\Command\\Put' => $baseDir . '/../lib/Command/Put.php', 'OCA\\Files\\Command\\RepairTree' => $baseDir . '/../lib/Command/RepairTree.php', 'OCA\\Files\\Command\\Scan' => $baseDir . '/../lib/Command/Scan.php', 'OCA\\Files\\Command\\ScanAppData' => $baseDir . '/../lib/Command/ScanAppData.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index 0946a5c39c2..e7b822e386d 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -42,7 +42,10 @@ class ComposerStaticInitFiles 'OCA\\Files\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', 'OCA\\Files\\Collaboration\\Resources\\Listener' => __DIR__ . '/..' . '/../lib/Collaboration/Resources/Listener.php', 'OCA\\Files\\Collaboration\\Resources\\ResourceProvider' => __DIR__ . '/..' . '/../lib/Collaboration/Resources/ResourceProvider.php', + 'OCA\\Files\\Command\\Delete' => __DIR__ . '/..' . '/../lib/Command/Delete.php', 'OCA\\Files\\Command\\DeleteOrphanedFiles' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanedFiles.php', + 'OCA\\Files\\Command\\Get' => __DIR__ . '/..' . '/../lib/Command/Get.php', + 'OCA\\Files\\Command\\Put' => __DIR__ . '/..' . '/../lib/Command/Put.php', 'OCA\\Files\\Command\\RepairTree' => __DIR__ . '/..' . '/../lib/Command/RepairTree.php', 'OCA\\Files\\Command\\Scan' => __DIR__ . '/..' . '/../lib/Command/Scan.php', 'OCA\\Files\\Command\\ScanAppData' => __DIR__ . '/..' . '/../lib/Command/ScanAppData.php', diff --git a/apps/files/l10n/zh_CN.js b/apps/files/l10n/zh_CN.js index 0d6978e5fbd..327e23ac172 100644 --- a/apps/files/l10n/zh_CN.js +++ b/apps/files/l10n/zh_CN.js @@ -109,6 +109,8 @@ OC.L10N.register( "Create new folder" : "创建新文件夹", "Upload file" : "上传文件", "Recent" : "最近", + "This file has the tag {tag}" : "这个文件有 {tag} 标签", + "This file has the tags {firstTags} and {lastTag}" : "这个文件有 {firstTags} 和 {lastTag} 标签", "Not favorited" : "未收藏", "Remove from favorites" : "从收藏中移除", "Add to favorites" : "添加到收藏夹", @@ -176,9 +178,11 @@ OC.L10N.register( "\"{displayName}\" action failed" : "\"{displayName}\" 操作执行失败", "Select all" : "全部选择", "Unselect all" : "取消全选", + "\"{displayName}\" batch action executed successfully" : "批量操作 \"{displayName}\" 运行成功", "ascending" : "升序", "descending" : "降序", "Sort list by {column} ({direction})" : "按 {column} ({direction}) 排序列表", + "This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "该列表因为性能原因没有完全加载。文件将会在您浏览列表的时候加载。", "Storage informations" : "存储信息", "{usedQuotaByte} used" : "{usedQuotaByte} 已使用", "{relative}% used" : "{relative}% 已使用", @@ -187,6 +191,7 @@ OC.L10N.register( "Choose file or folder to transfer" : "选择要转移的文件或文件夹", "Change" : "修改", "New owner" : "新的拥有者", + "Search for an account" : "搜索一个账户", "Choose a file or folder to transfer" : "选择要转移的文件或文件夹", "Transfer" : "转移", "Transfer {path} to {userid}" : "将 {path} 转移给 {userid}", @@ -225,6 +230,7 @@ OC.L10N.register( "Delete permanently" : "彻底删除", "Set up templates folder" : "设置模板文件夹", "Templates" : "模板", + "Create new templates folder" : "新建模板文件夹", "Unable to initialize the templates directory" : "无法初始化模板目录", "Toggle %1$s sublist" : "切换 %1$s 子列表", "Toggle grid view" : "切换网格视图", diff --git a/apps/files/l10n/zh_CN.json b/apps/files/l10n/zh_CN.json index 97a7c05d76c..149b51ed282 100644 --- a/apps/files/l10n/zh_CN.json +++ b/apps/files/l10n/zh_CN.json @@ -107,6 +107,8 @@ "Create new folder" : "创建新文件夹", "Upload file" : "上传文件", "Recent" : "最近", + "This file has the tag {tag}" : "这个文件有 {tag} 标签", + "This file has the tags {firstTags} and {lastTag}" : "这个文件有 {firstTags} 和 {lastTag} 标签", "Not favorited" : "未收藏", "Remove from favorites" : "从收藏中移除", "Add to favorites" : "添加到收藏夹", @@ -174,9 +176,11 @@ "\"{displayName}\" action failed" : "\"{displayName}\" 操作执行失败", "Select all" : "全部选择", "Unselect all" : "取消全选", + "\"{displayName}\" batch action executed successfully" : "批量操作 \"{displayName}\" 运行成功", "ascending" : "升序", "descending" : "降序", "Sort list by {column} ({direction})" : "按 {column} ({direction}) 排序列表", + "This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "该列表因为性能原因没有完全加载。文件将会在您浏览列表的时候加载。", "Storage informations" : "存储信息", "{usedQuotaByte} used" : "{usedQuotaByte} 已使用", "{relative}% used" : "{relative}% 已使用", @@ -185,6 +189,7 @@ "Choose file or folder to transfer" : "选择要转移的文件或文件夹", "Change" : "修改", "New owner" : "新的拥有者", + "Search for an account" : "搜索一个账户", "Choose a file or folder to transfer" : "选择要转移的文件或文件夹", "Transfer" : "转移", "Transfer {path} to {userid}" : "将 {path} 转移给 {userid}", @@ -223,6 +228,7 @@ "Delete permanently" : "彻底删除", "Set up templates folder" : "设置模板文件夹", "Templates" : "模板", + "Create new templates folder" : "新建模板文件夹", "Unable to initialize the templates directory" : "无法初始化模板目录", "Toggle %1$s sublist" : "切换 %1$s 子列表", "Toggle grid view" : "切换网格视图", diff --git a/apps/files/lib/Command/Delete.php b/apps/files/lib/Command/Delete.php new file mode 100644 index 00000000000..da535568702 --- /dev/null +++ b/apps/files/lib/Command/Delete.php @@ -0,0 +1,116 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files\Command; + +use OC\Core\Command\Info\FileUtils; +use OCA\Files_Sharing\SharedStorage; +use OCP\Files\Folder; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class Delete extends Command { + private FileUtils $fileUtils; + + public function __construct(FileUtils $fileUtils) { + $this->fileUtils = $fileUtils; + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('files:delete') + ->setDescription('Delete a file or folder') + ->addArgument('file', InputArgument::REQUIRED, "File id or path") + ->addOption('force', 'f', InputOption::VALUE_NONE, "Don't ask for configuration and don't output any warnings"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $fileInput = $input->getArgument('file'); + $inputIsId = is_numeric($fileInput); + $force = $input->getOption('force'); + $node = $this->fileUtils->getNode($fileInput); + + if (!$node) { + $output->writeln("<error>file $fileInput not found</error>"); + return 1; + } + + $deleteConfirmed = $force; + if (!$deleteConfirmed) { + /** @var QuestionHelper $helper */ + $helper = $this->getHelper('question'); + $storage = $node->getStorage(); + if (!$inputIsId && $storage->instanceOfStorage(SharedStorage::class) && $node->getInternalPath() === '') { + /** @var SharedStorage $storage */ + [,$user] = explode('/', $fileInput, 3); + $question = new ConfirmationQuestion("<info>$fileInput</info> in a shared file, do you want to unshare the file from <info>$user</info> instead of deleting the source file? [Y/n] ", true); + if ($helper->ask($input, $output, $question)) { + $storage->unshareStorage(); + return 0; + } else { + $node = $storage->getShare()->getNode(); + $output->writeln(""); + } + } + + $filesByUsers = $this->fileUtils->getFilesByUser($node); + if (count($filesByUsers) > 1) { + $output->writeln("Warning: the provided file is accessible by more than one user"); + $output->writeln(" all of the following users will lose access to the file when deleted:"); + $output->writeln(""); + foreach ($filesByUsers as $user => $filesByUser) { + $output->writeln($user . ":"); + foreach($filesByUser as $file) { + $output->writeln(" - " . $file->getPath()); + } + } + $output->writeln(""); + } + + if ($node instanceof Folder) { + $maybeContents = " and all it's contents"; + } else { + $maybeContents = ""; + } + $question = new ConfirmationQuestion("Delete " . $node->getPath() . $maybeContents . "? [y/N] ", false); + $deleteConfirmed = $helper->ask($input, $output, $question); + } + + if ($deleteConfirmed) { + if ($node->isDeletable()) { + $node->delete(); + } else { + $output->writeln("<error>File cannot be deleted, insufficient permissions.</error>"); + } + } + + return 0; + } + +} diff --git a/apps/files/lib/Command/Get.php b/apps/files/lib/Command/Get.php new file mode 100644 index 00000000000..7bdb4cb59ee --- /dev/null +++ b/apps/files/lib/Command/Get.php @@ -0,0 +1,89 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files\Command; + + +use OC\Core\Command\Info\FileUtils; +use OCP\Files\File; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Get extends Command { + private FileUtils $fileUtils; + + public function __construct(FileUtils $fileUtils) { + $this->fileUtils = $fileUtils; + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('files:get') + ->setDescription('Get the contents of a file') + ->addArgument('file', InputArgument::REQUIRED, "Source file id or Nextcloud path") + ->addArgument('output', InputArgument::OPTIONAL, "Target local file to output to, defaults to STDOUT"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $fileInput = $input->getArgument('file'); + $outputName = $input->getArgument('output'); + $node = $this->fileUtils->getNode($fileInput); + + if (!$node) { + $output->writeln("<error>file $fileInput not found</error>"); + return 1; + } + + if ($node instanceof File) { + $isTTY = stream_isatty(STDOUT); + if ($outputName === null && $isTTY && $node->getMimePart() !== 'text') { + $output->writeln([ + "<error>Warning: Binary output can mess up your terminal</error>", + " Use <info>occ files:get $fileInput -</info> to output it to the terminal anyway", + " Or <info>occ files:get $fileInput <FILE></info> to save to a file instead" + ]); + return 1; + } + $source = $node->fopen('r'); + if (!$source) { + $output->writeln("<error>Failed to open $fileInput for reading</error>"); + return 1; + } + $target = ($outputName === null || $outputName === '-') ? STDOUT : fopen($outputName, 'w'); + if (!$target) { + $output->writeln("<error>Failed to open $outputName for reading</error>"); + return 1; + } + + stream_copy_to_stream($source, $target); + return 0; + } else { + $output->writeln("<error>$fileInput is a directory</error>"); + return 1; + } + } + +} diff --git a/apps/files/lib/Command/Put.php b/apps/files/lib/Command/Put.php new file mode 100644 index 00000000000..f89ab8fb436 --- /dev/null +++ b/apps/files/lib/Command/Put.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files\Command; + + +use OC\Core\Command\Info\FileUtils; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Put extends Command { + private FileUtils $fileUtils; + private IRootFolder $rootFolder; + + public function __construct(FileUtils $fileUtils, IRootFolder $rootFolder) { + $this->fileUtils = $fileUtils; + $this->rootFolder = $rootFolder; + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('files:put') + ->setDescription('Write contents of a file') + ->addArgument('input', InputArgument::REQUIRED, "Source local path, use - to read from STDIN") + ->addArgument('file', InputArgument::REQUIRED, "Target Nextcloud file path to write to or fileid of existing file"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $fileOutput = $input->getArgument('file'); + $inputName = $input->getArgument('input'); + $node = $this->fileUtils->getNode($fileOutput); + + if ($node instanceof Folder) { + $output->writeln("<error>$fileOutput is a folder</error>"); + return 1; + } + if (!$node and is_numeric($fileOutput)) { + $output->writeln("<error>$fileOutput not found</error>"); + return 1; + } + + $source = ($inputName === null || $inputName === '-') ? STDIN : fopen($inputName, 'r'); + if (!$source) { + $output->writeln("<error>Failed to open $inputName</error>"); + return 1; + } + if ($node instanceof File) { + $target = $node->fopen('w'); + if (!$target) { + $output->writeln("<error>Failed to open $fileOutput</error>"); + return 1; + } + stream_copy_to_stream($source, $target); + } else { + $this->rootFolder->newFile($fileOutput, $source); + } + return 0; + } + +} diff --git a/apps/files/lib/Command/Scan.php b/apps/files/lib/Command/Scan.php index a59665c56e9..6c7a607d2af 100644 --- a/apps/files/lib/Command/Scan.php +++ b/apps/files/lib/Command/Scan.php @@ -58,6 +58,7 @@ class Scan extends Base { protected float $execTime = 0; protected int $foldersCounter = 0; protected int $filesCounter = 0; + protected int $errorsCounter = 0; private IRootFolder $root; private MetadataManager $metadataManager; @@ -148,10 +149,12 @@ class Scan extends Base { $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) { $output->writeln('Error while scanning, storage not available (' . $e->getMessage() . ')', OutputInterface::VERBOSITY_VERBOSE); + ++$this->errorsCounter; }); $scanner->listen('\OC\Files\Utils\Scanner', 'normalizedNameMismatch', function ($fullPath) use ($output) { $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>'); + ++$this->errorsCounter; }); try { @@ -164,14 +167,17 @@ class Scan extends Base { $output->writeln("<error>Home storage for user $user not writable or 'files' subdirectory missing</error>"); $output->writeln(' ' . $e->getMessage()); $output->writeln('Make sure you\'re running the scan command only as the user the web server runs as'); + ++$this->errorsCounter; } catch (InterruptedException $e) { # exit the function if ctrl-c has been pressed $output->writeln('Interrupted by user'); } catch (NotFoundException $e) { $output->writeln('<error>Path not found: ' . $e->getMessage() . '</error>'); + ++$this->errorsCounter; } catch (\Exception $e) { $output->writeln('<error>Exception during scan: ' . $e->getMessage() . '</error>'); $output->writeln('<error>' . $e->getTraceAsString() . '</error>'); + ++$this->errorsCounter; } } @@ -192,11 +198,6 @@ class Scan extends Base { $users = $input->getArgument('user_id'); } - # restrict the verbosity level to VERBOSITY_VERBOSE - if ($output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) { - $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); - } - # check quantity of users to be process and show it on the command line $users_total = count($users); if ($users_total === 0) { @@ -204,7 +205,7 @@ class Scan extends Base { return 1; } - $this->initTools(); + $this->initTools($output); $user_count = 0; foreach ($users as $user) { @@ -236,15 +237,19 @@ class Scan extends Base { /** * Initialises some useful tools for the Command */ - protected function initTools() { + protected function initTools(OutputInterface $output) { // Start the timer $this->execTime = -microtime(true); // Convert PHP errors to exceptions - set_error_handler([$this, 'exceptionErrorHandler'], E_ALL); + set_error_handler( + fn (int $severity, string $message, string $file, int $line): bool => + $this->exceptionErrorHandler($output, $severity, $message, $file, $line), + E_ALL + ); } /** - * Processes PHP errors as exceptions in order to be able to keep track of problems + * Processes PHP errors in order to be able to show them in the output * * @see https://www.php.net/manual/en/function.set-error-handler.php * @@ -252,15 +257,17 @@ class Scan extends Base { * @param string $message * @param string $file the filename that the error was raised in * @param int $line the line number the error was raised - * - * @throws \ErrorException */ - public function exceptionErrorHandler($severity, $message, $file, $line) { - if (!(error_reporting() & $severity)) { - // This error code is not included in error_reporting - return; + public function exceptionErrorHandler(OutputInterface $output, int $severity, string $message, string $file, int $line): bool { + if (($severity === E_DEPRECATED) || ($severity === E_USER_DEPRECATED)) { + // Do not show deprecation warnings + return false; } - throw new \ErrorException($message, 0, $severity, $file, $line); + $e = new \ErrorException($message, 0, $severity, $file, $line); + $output->writeln('<error>Error during scan: ' . $e->getMessage() . '</error>'); + $output->writeln('<error>' . $e->getTraceAsString() . '</error>', OutputInterface::VERBOSITY_VERY_VERBOSE); + ++$this->errorsCounter; + return true; } /** @@ -271,28 +278,18 @@ class Scan extends Base { $this->execTime += microtime(true); $headers = [ - 'Folders', 'Files', 'Elapsed time' + 'Folders', + 'Files', + 'Errors', + 'Elapsed time', ]; - - $this->showSummary($headers, null, $output); - } - - /** - * Shows a summary of operations - * - * @param string[] $headers - * @param string[] $rows - * @param OutputInterface $output - */ - protected function showSummary($headers, $rows, OutputInterface $output) { $niceDate = $this->formatExecTime(); - if (!$rows) { - $rows = [ - $this->foldersCounter, - $this->filesCounter, - $niceDate, - ]; - } + $rows = [ + $this->foldersCounter, + $this->filesCounter, + $this->errorsCounter, + $niceDate, + ]; $table = new Table($output); $table ->setHeaders($headers) diff --git a/apps/files/src/views/TemplatePicker.vue b/apps/files/src/views/TemplatePicker.vue index 79264d56074..a6bb9809b10 100644 --- a/apps/files/src/views/TemplatePicker.vue +++ b/apps/files/src/views/TemplatePicker.vue @@ -24,7 +24,7 @@ <NcModal v-if="opened" :clear-view-delay="-1" class="templates-picker" - size="normal" + size="large" @close="close"> <form class="templates-picker__form" :style="style" @@ -47,9 +47,6 @@ <!-- Cancel and submit --> <div class="templates-picker__buttons"> - <button @click="close"> - {{ t('files', 'Cancel') }} - </button> <input type="submit" class="primary" :value="t('files', 'Create')" @@ -75,7 +72,6 @@ import TemplatePreview from '../components/TemplatePreview.vue' const border = 2 const margin = 8 -const width = margin * 20 export default { name: 'TemplatePicker', @@ -136,6 +132,11 @@ export default { * @return {object} */ style() { + // Fallback to 16:9 landscape ratio + const ratio = this.provider.ratio ? this.provider.ratio : 1.77 + // Landscape templates should be wider than tall ones + // We fit 3 templates per row at max for landscape and 4 for portrait + const width = ratio > 1 ? margin * 30 : margin * 20 return { '--margin': margin + 'px', '--width': width + 'px', @@ -275,7 +276,7 @@ export default { &__buttons { display: flex; - justify-content: space-between; + justify-content: end; padding: calc(var(--margin) * 2) var(--margin); position: sticky; bottom: 0; diff --git a/apps/files_sharing/l10n/fr.js b/apps/files_sharing/l10n/fr.js index 17d94f9f7cd..b86c1064534 100644 --- a/apps/files_sharing/l10n/fr.js +++ b/apps/files_sharing/l10n/fr.js @@ -120,6 +120,7 @@ OC.L10N.register( "You cannot share to a Circle if the app is not enabled" : "Vous ne pouvez pas partager au Cercle si l'application n'est pas activée", "Please specify a valid circle" : "Veuillez entrer un cercle valide", "Sharing %s failed because the back end does not support room shares" : "Le partage %s a échoué parce que l'arrière-plan ne prend pas en charge les partages.", + "Sharing %s failed because the back end does not support ScienceMesh shares" : "Le partage de %s a échoué car le serveur ne supporte pas les partages ScienceMesh", "Unknown share type" : "Type de partage inconnu", "Not a directory" : "N'est pas un dossier", "Could not lock node" : "Impossible de verrouiller le nœud", @@ -225,6 +226,7 @@ OC.L10N.register( "Circle" : "Cercle", "Talk conversation" : "Conversation Talk", "Deck board" : "Tableau Deck", + "ScienceMesh" : "ScienceMesh", "on {server}" : "sur {server}", "Others with access" : "Autres utilisateurs ayant accès", "No other users with access found" : "Aucun autre utilisateur ayant un accès n'a été trouvé", diff --git a/apps/files_sharing/l10n/fr.json b/apps/files_sharing/l10n/fr.json index bcbab1f4560..4681e1c5504 100644 --- a/apps/files_sharing/l10n/fr.json +++ b/apps/files_sharing/l10n/fr.json @@ -118,6 +118,7 @@ "You cannot share to a Circle if the app is not enabled" : "Vous ne pouvez pas partager au Cercle si l'application n'est pas activée", "Please specify a valid circle" : "Veuillez entrer un cercle valide", "Sharing %s failed because the back end does not support room shares" : "Le partage %s a échoué parce que l'arrière-plan ne prend pas en charge les partages.", + "Sharing %s failed because the back end does not support ScienceMesh shares" : "Le partage de %s a échoué car le serveur ne supporte pas les partages ScienceMesh", "Unknown share type" : "Type de partage inconnu", "Not a directory" : "N'est pas un dossier", "Could not lock node" : "Impossible de verrouiller le nœud", @@ -223,6 +224,7 @@ "Circle" : "Cercle", "Talk conversation" : "Conversation Talk", "Deck board" : "Tableau Deck", + "ScienceMesh" : "ScienceMesh", "on {server}" : "sur {server}", "Others with access" : "Autres utilisateurs ayant accès", "No other users with access found" : "Aucun autre utilisateur ayant un accès n'a été trouvé", diff --git a/apps/files_sharing/lib/Scanner.php b/apps/files_sharing/lib/Scanner.php index baab7a862bd..d5a1c24418e 100644 --- a/apps/files_sharing/lib/Scanner.php +++ b/apps/files_sharing/lib/Scanner.php @@ -25,7 +25,7 @@ namespace OCA\Files_Sharing; -use OC\Files\ObjectStore\NoopScanner; +use OC\Files\ObjectStore\ObjectStoreScanner; /** * Scanner for SharedStorage @@ -72,7 +72,8 @@ class Scanner extends \OC\Files\Cache\Scanner { public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) { $sourceScanner = $this->getSourceScanner(); - if ($sourceScanner instanceof NoopScanner) { + if ($sourceScanner instanceof ObjectStoreScanner) { + // ObjectStoreScanner doesn't scan return []; } else { return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock); diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue index e45690ba2a0..bf0e94f35c1 100644 --- a/apps/files_sharing/src/components/SharingEntryLink.vue +++ b/apps/files_sharing/src/components/SharingEntryLink.vue @@ -161,6 +161,7 @@ class="share-link-password" :class="{ error: errors.password}" :disabled="saving" + :show-trailing-button="hasUnsavedPassword" :required="config.enforcePasswordForPublicLink" :value="hasUnsavedPassword ? share.newPassword : '***************'" icon="icon-password" diff --git a/apps/files_sharing/tests/External/ScannerTest.php b/apps/files_sharing/tests/External/ScannerTest.php index 2d2486737dc..8d077715b2d 100644 --- a/apps/files_sharing/tests/External/ScannerTest.php +++ b/apps/files_sharing/tests/External/ScannerTest.php @@ -26,9 +26,11 @@ namespace OCA\Files_Sharing\Tests\External; use OCA\Files_Sharing\External\Scanner; use Test\TestCase; +/** + * @group DB + */ class ScannerTest extends TestCase { - /** @var \OCA\Files_Sharing\External\Scanner */ - protected $scanner; + protected Scanner $scanner; /** @var \OCA\Files_Sharing\External\Storage|\PHPUnit\Framework\MockObject\MockObject */ protected $storage; /** @var \OC\Files\Cache\Cache|\PHPUnit\Framework\MockObject\MockObject */ diff --git a/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php b/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php index 70338a469d3..842721eeac9 100644 --- a/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php +++ b/apps/files_trashbin/lib/UserMigration/TrashbinMigrator.php @@ -67,7 +67,7 @@ class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator { /** * {@inheritDoc} */ - public function getEstimatedExportSize(IUser $user): int { + public function getEstimatedExportSize(IUser $user): int|float { $uid = $user->getUID(); try { @@ -75,7 +75,7 @@ class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator { if (!$trashbinFolder instanceof Folder) { return 0; } - return (int)ceil($trashbinFolder->getSize() / 1024); + return ceil($trashbinFolder->getSize() / 1024); } catch (\Throwable $e) { return 0; } diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index 9141e6c4c65..cd13e74e786 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -608,7 +608,12 @@ class Storage { foreach ($versions as $version) { $internalPath = $version->getInternalPath(); \OC_Hook::emit('\OCP\Versions', 'preDelete', ['path' => $internalPath, 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]); - $versionsMapper->delete($versionEntities[$version->getId()]); + + $versionEntity = $versionEntities[$version->getId()]; + if (!is_null($versionEntity)) { + $versionsMapper->delete($versionEntity); + } + $version->delete(); \OC_Hook::emit('\OCP\Versions', 'delete', ['path' => $internalPath, 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT]); } diff --git a/apps/settings/css/settings.css b/apps/settings/css/settings.css index 49c99f687c5..528a85ab09c 100644 --- a/apps/settings/css/settings.css +++ b/apps/settings/css/settings.css @@ -1 +1 @@ -input#openid,input#webdav{width:20em}.clear{clear:both}.nav-icon-personal-settings{background-image:var(--icon-personal-dark)}.nav-icon-security{background-image:var(--icon-toggle-filelist-dark)}.nav-icon-clientsbox{background-image:var(--icon-change-dark)}.nav-icon-federated-cloud{background-image:var(--icon-share-dark)}.nav-icon-second-factor-backup-codes,.nav-icon-ssl-root-certificate{background-image:var(--icon-password-dark)}#personal-settings-avatar-container{display:inline-grid;grid-template-columns:1fr;grid-template-rows:2fr 1fr 2fr;vertical-align:top}.profile-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-show-container{width:100%}.personal-settings-setting-box .section{padding:10px 30px}.personal-settings-setting-box .section .headerbar-label{margin-bottom:0}.personal-settings-setting-box .section input[type=text],.personal-settings-setting-box .section input[type=email],.personal-settings-setting-box .section input[type=tel],.personal-settings-setting-box .section input[type=url]{width:100%}.personal-settings-setting-box-profile{grid-row:3/5}.personal-settings-setting-box-detail{grid-row:5}.personal-settings-setting-box-detail--without-profile{grid-row:3}select#timezone{width:100%}#personal-settings{display:grid;padding:20px;max-width:1700px;grid-template-columns:repeat(auto-fill, minmax(300px, 1fr));grid-column-gap:10px}#personal-settings .section{padding:10px 10px;border:0}#personal-settings .section h2{margin-bottom:12px}#personal-settings .section h3>label{font-weight:bold}#personal-settings .personal-info{margin-right:10%;margin-bottom:12px;margin-top:12px}#personal-settings .personal-info[class^=icon-],#personal-settings .personal-info[class*=" icon-"]{background-position:0px 2px;padding-left:30px;opacity:.7}.development-notice{text-align:center}.development-notice a:not(.link-button){text-decoration:underline}.development-notice a:not(.link-button):hover{background-color:var(--color-primary-element-hover)}.link-button{display:inline-block;margin:16px;padding:14px 20px;background-color:var(--color-primary);color:#fff;border-radius:var(--border-radius-pill);border:1px solid var(--color-primary);box-shadow:0 2px 9px var(--color-box-shadow)}.link-button:active,.link-button:hover,.link-button:focus{color:var(--color-primary);background-color:var(--color-primary-text);border-color:var(--color-primary) !important}.link-button.icon-file{padding-left:48px;background-position:24px}.personal-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-settings-container:after{clear:both}.personal-settings-container>div h3{position:relative;display:inline-flex;flex-wrap:nowrap;justify-content:flex-start;width:100%;align-items:center;gap:8px}.personal-settings-container>div h3>label{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.personal-settings-container>div>form span[class^=icon-checkmark],.personal-settings-container>div>form span[class^=icon-error]{position:relative;right:8px;top:-28px;pointer-events:none;float:right}.personal-settings-container .verify{position:relative;left:100%;top:0;height:0}.personal-settings-container .verify img{padding:12px 7px 6px}.personal-settings-container .verify-action{cursor:pointer}.personal-settings-container input:disabled{background-color:#fff;color:#000;border:none;opacity:100}.verification-dialog{display:none;right:-9px;top:40px;width:275px}.verification-dialog p{padding:10px}.verification-dialog .verificationCode{font-family:monospace;display:block;overflow-wrap:break-word}.federation-menu{position:relative;cursor:pointer;width:44px;height:44px;padding:10px;margin:0;background:none;border:none}.federation-menu:hover,.federation-menu:focus{background-color:var(--color-background-hover);border-radius:var(--border-radius-pill)}.federation-menu:hover .icon-federation-menu,.federation-menu:focus .icon-federation-menu{opacity:.8}.federation-menu .icon-federation-menu{padding-left:16px;background-size:16px;background-position:left center;opacity:.3;cursor:inherit}.federation-menu .icon-federation-menu .icon-triangle-s{display:inline-block;vertical-align:middle;cursor:inherit}.federation-menu .federationScopeMenu{top:44px}.federation-menu .federationScopeMenu.popovermenu .menuitem{font-size:12.8px;line-height:1.6em}.federation-menu .federationScopeMenu.popovermenu .menuitem .menuitem-text-detail{opacity:.75}.federation-menu .federationScopeMenu.popovermenu .menuitem.active{box-shadow:inset 2px 0 var(--color-primary)}.federation-menu .federationScopeMenu.popovermenu .menuitem.active .menuitem-text{font-weight:bold}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled{opacity:.5;cursor:default}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled *{cursor:default}.clientsbox img{height:60px}#sslCertificate tr.expired{background-color:rgba(255,0,0,.5)}#sslCertificate td{padding:5px}#displaynameerror,#displaynamechanged{display:none}input#identity{width:20em}#showWizard{display:inline-block}.msg.success{color:#fff;background-color:#47a447;padding:3px}.msg.error{color:#fff;background-color:#d2322d;padding:3px}table.nostyle label{margin-right:2em}table.nostyle td{padding:.2em 0}#security-password #passwordform{display:flex;flex-wrap:wrap;flex-direction:column;gap:1rem}#security-password #passwordform .input-control{display:flex;flex-wrap:wrap;flex-direction:column}#security-password #passwordform .input-control label{margin-bottom:.5rem}#security-password #passwordform #pass1,#security-password #passwordform .personal-show-container{flex-shrink:1;width:300px;min-width:150px}#security-password #passwordform .personal-show-container #pass2{position:relative;top:.5rem}#security-password #passwordform .personal-show-container .personal-show-label{top:34px !important;margin-right:0;margin-top:0 !important;right:3px}#security-password #passwordform #pass2{width:100%}#security-password #passwordform .password-state{display:inline-block}#security-password #passwordform .strengthify-wrapper{position:absolute;left:0;width:100%;border-radius:0 0 2px 2px;margin-top:5px;overflow:hidden;height:3px}#two-factor-auth h3{margin-top:24px}#two-factor-auth li>div{margin-left:20px}#two-factor-auth .two-factor-provider-settings-icon{width:16px;height:16px;vertical-align:sub;filter:var(--background-invert-if-dark)}.isgroup .groupname{width:85%;display:block;overflow:hidden;text-overflow:ellipsis}.isgroup.active .groupname{width:65%}li.active .delete,li.active .rename{display:block}.app-navigation-entry-utils .delete,.app-navigation-entry-utils .rename{display:none}#usersearchform{position:absolute;top:2px;right:0}#usersearchform input{width:150px}#usersearchform label{font-weight:bold}table.grid{width:100%}table.grid th{height:2em;color:#999;border-bottom:1px solid var(--color-border);padding:0 .5em;padding-left:.8em;text-align:left;font-weight:normal}table.grid td{border-bottom:1px solid var(--color-border);padding:0 .5em;padding-left:.8em;text-align:left;font-weight:normal}td.name,th.name{padding-left:.8em;min-width:5em;max-width:12em;text-overflow:ellipsis;overflow:hidden}td.password,th.password{padding-left:.8em}td.password>img,th.password>img{visibility:hidden}td.displayName>img,th.displayName>img{visibility:hidden}td.password,td.mailAddress,th.password,th.mailAddress{min-width:5em;max-width:12em;cursor:pointer}td.password span,td.mailAddress span,th.password span,th.mailAddress span{width:90%;display:inline-block;text-overflow:ellipsis;overflow:hidden}td.mailAddress,th.mailAddress{cursor:pointer}td.password>span,th.password>span{margin-right:1.2em;color:#c7c7c7}span.usersLastLoginTooltip{white-space:nowrap}#app-content>svg.app-filter{float:left;height:0;width:0}#app-category-app-bundles{margin-bottom:20px}.appinfo{margin:1em 40px}#app-navigation img{margin-bottom:-3px;margin-right:6px;width:16px}#app-navigation li span.no-icon{padding-left:32px}#app-navigation ul li.active>span.utils .delete,#app-navigation ul li.active>span.utils .rename{display:block}#app-navigation .appwarning{background:#fcc}#app-navigation.appwarning:hover{background:#fbb}#app-navigation .app-external{color:var(--color-text-maxcontrast)}span.version{margin-left:1em;margin-right:1em;color:var(--color-text-maxcontrast)}.app-version{color:var(--color-text-maxcontrast)}.app-level span{color:var(--color-text-maxcontrast);background-color:rgba(0,0,0,0);border:1px solid var(--color-text-maxcontrast);border-radius:var(--border-radius);padding:3px 6px}.app-level a{padding:10px;margin:-6px;white-space:nowrap}.app-level .official{background-position:left center;background-position:5px center;padding-left:25px}.app-level .supported{border-color:var(--color-success);background-position:left center;background-position:5px center;padding-left:25px;color:var(--color-success)}.app-score{position:relative;top:4px;opacity:.5}.app-settings-content #searchresults{display:none}#apps-list.store .section{border:0}#apps-list.store .app-name{display:block;margin:5px 0}#apps-list.store .app-name,#apps-list.store .app-image *{cursor:pointer}#apps-list.store .app-summary{opacity:.7}#apps-list.store .app-image-icon .icon-settings-dark{width:100%;height:150px;background-size:45px;opacity:.5}#apps-list.store .app-score-image{height:14px}#apps-list.store .actions{margin-top:10px}#app-sidebar #app-details-view h2 .icon-settings-dark,#app-sidebar #app-details-view h2 svg{display:inline-block;width:16px;height:16px;margin-right:10px;opacity:.7}#app-sidebar #app-details-view .app-level{clear:right;width:100%}#app-sidebar #app-details-view .app-level .supported,#app-sidebar #app-details-view .app-level .official{vertical-align:top}#app-sidebar #app-details-view .app-level .app-score-image{float:right}#app-sidebar #app-details-view .app-author,#app-sidebar #app-details-view .app-licence{color:var(--color-text-maxcontrast)}#app-sidebar #app-details-view .app-dependencies{margin:10px 0}#app-sidebar #app-details-view .app-description p{margin:10px 0}#app-sidebar #app-details-view .close{position:absolute;top:0;right:0;padding:14px;opacity:.5;z-index:1;width:44px;height:44px}#app-sidebar #app-details-view .actions{display:flex;align-items:center}#app-sidebar #app-details-view .actions .app-groups{padding:5px}#app-sidebar #app-details-view .appslink{text-decoration:underline;margin-right:5px}#app-sidebar #app-details-view .app-level,#app-sidebar #app-details-view .actions,#app-sidebar #app-details-view .documentation,#app-sidebar #app-details-view .app-dependencies,#app-sidebar #app-details-view .app-description{margin:20px 0}@media only screen and (min-width: 1601px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1600px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1400px){.store .section{width:33%}.with-app-sidebar .store .section{width:50%}}@media only screen and (max-width: 900px){.store .section{width:50%}.with-app-sidebar .store .section{width:100%}}@media only screen and (max-width: 1024px){.store .section{width:50%}}@media only screen and (max-width: 480px){.store .section{width:100%}}@media only screen and (max-width: 900px){.apps-list.installed .app-version,.apps-list.installed .app-level{display:none !important}}@media only screen and (max-width: 500px){.apps-list.installed .app-groups{display:none !important}}.section{margin-bottom:0}.section:not(:last-child){border-bottom:1px solid var(--color-border)}.section h2{margin-bottom:22px}.section h2 .icon-info{padding:6px 20px;vertical-align:text-bottom;display:inline-block}.followupsection{display:block;padding:0 30px 30px 30px}.app-image{position:relative;height:150px;opacity:1;overflow:hidden}.app-name,.app-version,.app-score,.app-level{display:inline-block}.app-description-toggle-show,.app-description-toggle-hide{clear:both;padding:7px 0;cursor:pointer;opacity:.5}.app-description-container{clear:both;position:relative;top:7px}.app-description{clear:both}#app-category-1{margin-bottom:18px}#app-category-925{text-transform:capitalize}.app-dependencies{color:#ce3702}.missing-dependencies{list-style:initial;list-style-type:initial;list-style-position:inside}.apps-list{display:flex;flex-wrap:wrap;align-content:flex-start}.apps-list .section{cursor:pointer}.apps-list .app-list-move{transition:transform 1s}.apps-list #app-list-update-all{margin-left:10px}.apps-list .toolbar{height:60px;padding:8px;padding-left:60px;width:100%;background-color:var(--color-main-background);position:sticky;top:0;z-index:1;display:flex;align-items:center}.apps-list.installed{margin-bottom:100px}.apps-list.installed .apps-list-container{display:table;width:100%;height:auto;margin-top:60px}.apps-list.installed .section{display:table-row;padding:0;margin:0}.apps-list.installed .section>*{display:table-cell;height:initial;vertical-align:middle;float:none;border-bottom:1px solid var(--color-border);padding:6px;box-sizing:border-box}.apps-list.installed .section.selected{background-color:var(--color-background-dark)}.apps-list.installed .groups-enable{margin-top:0}.apps-list.installed .groups-enable label{margin-right:3px}.apps-list.installed .app-image{width:44px;height:auto;text-align:right}.apps-list.installed .app-image-icon svg,.apps-list.installed .app-image-icon .icon-settings-dark{margin-top:5px;width:20px;height:20px;opacity:.5;background-size:cover;display:inline-block}.apps-list.installed .actions{text-align:right}.apps-list.installed .actions .icon-loading-small{display:inline-block;top:4px;margin-right:10px}.apps-list:not(.installed) .app-image-icon svg{position:absolute;bottom:43px;width:64px;height:64px;opacity:.1}.apps-list.hidden{display:none}.apps-list .section{position:relative;flex:0 0 auto}.apps-list .section h2.app-name{display:block;margin:8px 0}.apps-list .section:hover{background-color:var(--color-background-dark)}.apps-list .app-description p{margin:10px 0}.apps-list .app-description ul{list-style:disc}.apps-list .app-description ol{list-style:decimal}.apps-list .app-description ol ol,.apps-list .app-description ol ul{padding-left:15px}.apps-list .app-description>ul,.apps-list .app-description>ol{margin-left:19px}.apps-list .app-description ul ol,.apps-list .app-description ul ul{padding-left:15px}.apps-list .apps-header{display:table-row;position:relative}.apps-list .apps-header div{display:table-cell;height:70px}.apps-list .apps-header h2{display:table-cell;position:absolute;padding-left:6px;padding-top:15px}.apps-list .apps-header h2 .enable{position:relative;top:-1px;margin-left:12px}.apps-list .apps-header h2+.section{margin-top:50px}#apps-list-search .section h2{margin-bottom:0}#log{white-space:normal;margin-bottom:14px}#lessLog{display:none}table.grid td.date{white-space:nowrap}#log-section p{margin-top:20px}#security-warning-state-ok span,#security-warning-state-warning span,#security-warning-state-failure span,#security-warning-state-loading span{vertical-align:middle}#security-warning-state-ok span.message,#security-warning-state-warning span.message,#security-warning-state-failure span.message,#security-warning-state-loading span.message{padding:12px}#security-warning-state-ok span.icon,#security-warning-state-warning span.icon,#security-warning-state-failure span.icon,#security-warning-state-loading span.icon{width:32px;height:32px;background-position:center center;display:inline-block;border-radius:50%}#security-warning-state-ok span.icon-checkmark-white,#security-warning-state-warning span.icon-checkmark-white,#security-warning-state-failure span.icon-checkmark-white,#security-warning-state-loading span.icon-checkmark-white{background-color:var(--color-success)}#security-warning-state-ok span.icon-error-white,#security-warning-state-warning span.icon-error-white,#security-warning-state-failure span.icon-error-white,#security-warning-state-loading span.icon-error-white{background-color:var(--color-warning)}#security-warning-state-ok span.icon-close-white,#security-warning-state-warning span.icon-close-white,#security-warning-state-failure span.icon-close-white,#security-warning-state-loading span.icon-close-white{background-color:var(--color-error)}#shareAPI p{padding-bottom:.8em}#shareAPI input#shareapiExpireAfterNDays{width:40px}#shareAPI .indent{padding-left:28px}#shareAPI .double-indent{padding-left:56px}#shareAPI .nocheckbox{padding-left:20px}#shareApiDefaultPermissionsSection label{margin-right:20px}#fileSharingSettings h3{display:inline-block}#publicShareDisclaimerText{width:calc(100% - 23px);max-width:600px;height:150px;margin-left:20px;box-sizing:border-box}.icon-info{padding:11px 20px;vertical-align:text-bottom;opacity:.5}#two-factor-auth h2,#shareAPI h2,#mail_general_settings h2{display:inline-block}.mail_settings p label:first-child{display:inline-block;width:300px;text-align:right}.mail_settings p select:nth-child(2),.mail_settings p input:not([type=button]){width:143px}#mail_smtpport{width:60px}.cronlog{margin-left:10px}.status{display:inline-block;height:16px;width:16px;vertical-align:text-bottom}.status.success{border-radius:50%}#selectGroups select{box-sizing:border-box;display:inline-block;height:36px;padding:7px 10px}#log .log-message{word-break:break-all;min-width:180px}span.success{background-color:var(--color-success);border-radius:var(--border-radius)}span.error{background-color:var(--color-error)}span.indeterminate{background-color:var(--color-warning);border-radius:40% 0}doesnotexist:-o-prefocus,.strengthify-wrapper{left:185px;width:129px}.trusted-domain-warning{color:#fff;padding:5px;background:#ce3702;border-radius:5px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}#postsetupchecks ul{margin-left:44px;list-style:disc}#postsetupchecks ul li{margin:10px 0}#postsetupchecks ul ul{list-style:circle}#postsetupchecks .loading{height:50px;background-position:left center}#postsetupchecks .errors,#postsetupchecks .errors a{color:var(--color-error)}#postsetupchecks .warnings,#postsetupchecks .warnings a{color:var(--color-warning)}#postsetupchecks .hint{margin:20px 0}#security-warning a{text-decoration:underline}#security-warning .extra-top-margin{margin-top:12px}#admin-tips li{list-style:initial}#admin-tips li a{display:inline-block;padding:3px 0}#warning{color:red}.settings-hint{margin-top:-12px;margin-bottom:12px;opacity:.7}#body-settings #app-content.user-list-grid{display:grid;grid-column-gap:20px;grid-auto-rows:minmax(60px, max-content)}#body-settings #app-content.user-list-grid .row{display:flex;display:grid;min-height:60px;grid-row-start:span 1;grid-gap:3px;align-items:center;grid-template-columns:44px minmax(190px, 1fr) minmax(160px, 1fr) minmax(160px, 1fr) minmax(240px, 1fr) minmax(240px, 1fr) repeat(auto-fit, minmax(160px, 1fr));border-bottom:var(--color-border) 1px solid}#body-settings #app-content.user-list-grid .row.disabled{opacity:.5}#body-settings #app-content.user-list-grid .row .name,#body-settings #app-content.user-list-grid .row .password,#body-settings #app-content.user-list-grid .row .mailAddress,#body-settings #app-content.user-list-grid .row .languages,#body-settings #app-content.user-list-grid .row .storageLocation,#body-settings #app-content.user-list-grid .row .userBackend,#body-settings #app-content.user-list-grid .row .lastLogin{min-width:160px}#body-settings #app-content.user-list-grid .row .name doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .name .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .password doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .password .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .mailAddress doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .mailAddress .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .languages doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .languages .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .storageLocation doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .storageLocation .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .userBackend doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .userBackend .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .lastLogin doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .lastLogin .strengthify-wrapper{color:var(--color-text-dark);vertical-align:baseline;text-overflow:ellipsis}#body-settings #app-content.user-list-grid .row:not(.row--editable).name,#body-settings #app-content.user-list-grid .row:not(.row--editable).password,#body-settings #app-content.user-list-grid .row:not(.row--editable).displayName,#body-settings #app-content.user-list-grid .row:not(.row--editable).mailAddress,#body-settings #app-content.user-list-grid .row:not(.row--editable).userBackend,#body-settings #app-content.user-list-grid .row:not(.row--editable).languages{overflow:hidden}#body-settings #app-content.user-list-grid .row:not(.row--editable) .groups,#body-settings #app-content.user-list-grid .row:not(.row--editable) .subadmins{overflow:auto;max-height:100%}#body-settings #app-content.user-list-grid .row .groups,#body-settings #app-content.user-list-grid .row .subadmins,#body-settings #app-content.user-list-grid .row .quota{min-width:160px}#body-settings #app-content.user-list-grid .row .groups .multiselect,#body-settings #app-content.user-list-grid .row .subadmins .multiselect,#body-settings #app-content.user-list-grid .row .quota .multiselect{width:100%;color:var(--color-text-dark);vertical-align:baseline}#body-settings #app-content.user-list-grid .row .groups progress,#body-settings #app-content.user-list-grid .row .subadmins progress,#body-settings #app-content.user-list-grid .row .quota progress{max-width:95%}#body-settings #app-content.user-list-grid .row .obfuscated{width:400px;opacity:.7}#body-settings #app-content.user-list-grid .row .userActions{display:flex;justify-content:flex-end;position:sticky;right:0px;min-width:88px;background-color:var(--color-main-background)}#body-settings #app-content.user-list-grid .row.row--editable .userActions{z-index:10}#body-settings #app-content.user-list-grid .row .subtitle{color:var(--color-text-maxcontrast);vertical-align:baseline}#body-settings #app-content.user-list-grid .row#grid-header{position:sticky;align-self:normal;background-color:var(--color-main-background);z-index:100;top:0}#body-settings #app-content.user-list-grid .row#grid-header.sticky{box-shadow:0 -2px 10px 1px var(--color-box-shadow)}#body-settings #app-content.user-list-grid .row#grid-header{color:var(--color-text-maxcontrast);border-bottom-width:thin}#body-settings #app-content.user-list-grid .row#grid-header #headerDisplayName,#body-settings #app-content.user-list-grid .row#grid-header #headerPassword,#body-settings #app-content.user-list-grid .row#grid-header #headerAddress,#body-settings #app-content.user-list-grid .row#grid-header #headerGroups,#body-settings #app-content.user-list-grid .row#grid-header #headerSubAdmins,#body-settings #app-content.user-list-grid .row#grid-header #theHeaderUserBackend,#body-settings #app-content.user-list-grid .row#grid-header #theHeaderLastLogin,#body-settings #app-content.user-list-grid .row#grid-header #headerQuota,#body-settings #app-content.user-list-grid .row#grid-header #theHeaderStorageLocation,#body-settings #app-content.user-list-grid .row#grid-header #headerLanguages{padding-left:7px;text-transform:none;color:var(--color-text-maxcontrast);vertical-align:baseline}#body-settings #app-content.user-list-grid .row:hover input:not([type=submit]):not(:focus):not(:active){border-color:var(--color-border) !important}#body-settings #app-content.user-list-grid .row:hover:not(#grid-header){box-shadow:5px 0 0 var(--color-primary-element) inset}#body-settings #app-content.user-list-grid .row>form{width:100%}#body-settings #app-content.user-list-grid .row>div,#body-settings #app-content.user-list-grid .row>.displayName>form,#body-settings #app-content.user-list-grid .row>form{grid-row:1;display:inline-flex;color:var(--color-text-lighter);flex-grow:1}#body-settings #app-content.user-list-grid .row>div>input:not(:focus):not(:active),#body-settings #app-content.user-list-grid .row>.displayName>form>input:not(:focus):not(:active),#body-settings #app-content.user-list-grid .row>form>input:not(:focus):not(:active){border-color:rgba(0,0,0,0);cursor:pointer}#body-settings #app-content.user-list-grid .row>div>input:focus+.icon-confirm,#body-settings #app-content.user-list-grid .row>div>input:active+.icon-confirm,#body-settings #app-content.user-list-grid .row>.displayName>form>input:focus+.icon-confirm,#body-settings #app-content.user-list-grid .row>.displayName>form>input:active+.icon-confirm,#body-settings #app-content.user-list-grid .row>form>input:focus+.icon-confirm,#body-settings #app-content.user-list-grid .row>form>input:active+.icon-confirm{display:block !important}#body-settings #app-content.user-list-grid .row>div:not(.userActions)>input:not([type=submit]),#body-settings #app-content.user-list-grid .row>.displayName>form:not(.userActions)>input:not([type=submit]),#body-settings #app-content.user-list-grid .row>form:not(.userActions)>input:not([type=submit]){width:100%;min-width:0}#body-settings #app-content.user-list-grid .row>div.name,#body-settings #app-content.user-list-grid .row>.displayName>form.name,#body-settings #app-content.user-list-grid .row>form.name{word-break:break-all}#body-settings #app-content.user-list-grid .row>div.displayName>input,#body-settings #app-content.user-list-grid .row>div.mailAddress>input,#body-settings #app-content.user-list-grid .row>.displayName>form.displayName>input,#body-settings #app-content.user-list-grid .row>.displayName>form.mailAddress>input,#body-settings #app-content.user-list-grid .row>form.displayName>input,#body-settings #app-content.user-list-grid .row>form.mailAddress>input{text-overflow:ellipsis;flex-grow:1}#body-settings #app-content.user-list-grid .row>div.name,#body-settings #app-content.user-list-grid .row>div.userBackend,#body-settings #app-content.user-list-grid .row>.displayName>form.name,#body-settings #app-content.user-list-grid .row>.displayName>form.userBackend,#body-settings #app-content.user-list-grid .row>form.name,#body-settings #app-content.user-list-grid .row>form.userBackend{line-height:1.3em;max-height:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}#body-settings #app-content.user-list-grid .row>div.name .subtitle,#body-settings #app-content.user-list-grid .row>.displayName>form.name .subtitle,#body-settings #app-content.user-list-grid .row>form.name .subtitle{color:var(--color-main-text)}#body-settings #app-content.user-list-grid .row>div.quota,#body-settings #app-content.user-list-grid .row>.displayName>form.quota,#body-settings #app-content.user-list-grid .row>form.quota{display:flex;justify-content:left;white-space:nowrap;position:relative}#body-settings #app-content.user-list-grid .row>div.quota progress,#body-settings #app-content.user-list-grid .row>.displayName>form.quota progress,#body-settings #app-content.user-list-grid .row>form.quota progress{width:150px;margin-top:35px;height:3px}#body-settings #app-content.user-list-grid .row>div .icon-confirm,#body-settings #app-content.user-list-grid .row>.displayName>form .icon-confirm,#body-settings #app-content.user-list-grid .row>form .icon-confirm{flex:0 0 auto;cursor:pointer}#body-settings #app-content.user-list-grid .row>div .icon-confirm:not(:active),#body-settings #app-content.user-list-grid .row>.displayName>form .icon-confirm:not(:active),#body-settings #app-content.user-list-grid .row>form .icon-confirm:not(:active){display:none}#body-settings #app-content.user-list-grid .row>div.avatar,#body-settings #app-content.user-list-grid .row>.displayName>form.avatar,#body-settings #app-content.user-list-grid .row>form.avatar{height:32px;width:32px;margin:6px}#body-settings #app-content.user-list-grid .row>div.avatar img,#body-settings #app-content.user-list-grid .row>.displayName>form.avatar img,#body-settings #app-content.user-list-grid .row>form.avatar img{display:block}#body-settings #app-content.user-list-grid .row>div.userActions,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions,#body-settings #app-content.user-list-grid .row>form.userActions{display:flex;justify-content:flex-end}#body-settings #app-content.user-list-grid .row>div.userActions #newsubmit,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions #newsubmit,#body-settings #app-content.user-list-grid .row>form.userActions #newsubmit{width:100%}#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions{position:relative;display:flex;align-items:center;background-color:var(--color-main-background)}#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more{width:44px;height:44px;opacity:.5;cursor:pointer}#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more:focus,#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more:hover,#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more:active,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more:focus,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more:hover,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more:active,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more:focus,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more:hover,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more:active{opacity:.7;background-color:var(--color-background-dark)}#body-settings #app-content.user-list-grid .row>div.userActions .feedback,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .feedback,#body-settings #app-content.user-list-grid .row>form.userActions .feedback{display:flex;align-items:center;white-space:nowrap;transition:opacity 200ms ease-in-out}#body-settings #app-content.user-list-grid .row>div.userActions .feedback .icon-checkmark,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .feedback .icon-checkmark,#body-settings #app-content.user-list-grid .row>form.userActions .feedback .icon-checkmark{opacity:.5;margin-right:5px}#body-settings #app-content.user-list-grid .row>div .multiselect.multiselect-vue,#body-settings #app-content.user-list-grid .row>.displayName>form .multiselect.multiselect-vue,#body-settings #app-content.user-list-grid .row>form .multiselect.multiselect-vue{min-width:100%;width:100%}#body-settings #app-content.user-list-grid .infinite-loading-container{display:flex;align-items:center;justify-content:center;grid-row-start:span 4}#body-settings #app-content.user-list-grid .users-list-end{opacity:.5;user-select:none}.animated{animation:blink-animation 1s steps(5, start) 4}@keyframes blink-animation{to{opacity:.6}}@-webkit-keyframes blink-animation{to{opacity:1}}/*# sourceMappingURL=settings.css.map */ +input#openid,input#webdav{width:20em}.clear{clear:both}.nav-icon-personal-settings{background-image:var(--icon-personal-dark)}.nav-icon-security{background-image:var(--icon-toggle-filelist-dark)}.nav-icon-clientsbox{background-image:var(--icon-change-dark)}.nav-icon-federated-cloud{background-image:var(--icon-share-dark)}.nav-icon-second-factor-backup-codes,.nav-icon-ssl-root-certificate{background-image:var(--icon-password-dark)}#personal-settings-avatar-container{display:inline-grid;grid-template-columns:1fr;grid-template-rows:2fr 1fr 2fr;vertical-align:top}.profile-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-show-container{width:100%}.personal-settings-setting-box .section{padding:10px 30px}.personal-settings-setting-box .section .headerbar-label{margin-bottom:0}.personal-settings-setting-box .section input[type=text],.personal-settings-setting-box .section input[type=email],.personal-settings-setting-box .section input[type=tel],.personal-settings-setting-box .section input[type=url]{width:100%}.personal-settings-setting-box-profile{grid-row:3/5}.personal-settings-setting-box-detail{grid-row:5}.personal-settings-setting-box-detail--without-profile{grid-row:3}select#timezone{width:100%}#personal-settings{display:grid;padding:20px;max-width:1700px;grid-template-columns:repeat(auto-fill, minmax(300px, 1fr));grid-column-gap:10px}#personal-settings .section{padding:10px 10px;border:0}#personal-settings .section h2{margin-bottom:12px}#personal-settings .section h3>label{font-weight:bold}#personal-settings .personal-info{margin-right:10%;margin-bottom:12px;margin-top:12px}#personal-settings .personal-info[class^=icon-],#personal-settings .personal-info[class*=" icon-"]{background-position:0px 2px;padding-left:30px;opacity:.7}.development-notice{text-align:center}.development-notice a:not(.link-button){text-decoration:underline}.development-notice a:not(.link-button):hover{background-color:var(--color-primary-element-hover)}.link-button{display:inline-block;margin:16px;padding:14px 20px;background-color:var(--color-primary);color:#fff;border-radius:var(--border-radius-pill);border:1px solid var(--color-primary);box-shadow:0 2px 9px var(--color-box-shadow)}.link-button:active,.link-button:hover,.link-button:focus{color:var(--color-primary);background-color:var(--color-primary-text);border-color:var(--color-primary) !important}.link-button.icon-file{padding-left:48px;background-position:24px}.personal-settings-container{display:inline-grid;grid-template-columns:1fr 1fr 1fr}.personal-settings-container:after{clear:both}.personal-settings-container>div h3{position:relative;display:inline-flex;flex-wrap:nowrap;justify-content:flex-start;width:100%;align-items:center;gap:8px}.personal-settings-container>div h3>label{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.personal-settings-container>div>form span[class^=icon-checkmark],.personal-settings-container>div>form span[class^=icon-error]{position:relative;right:8px;top:-28px;pointer-events:none;float:right}.personal-settings-container .verify{position:relative;left:100%;top:0;height:0}.personal-settings-container .verify img{padding:12px 7px 6px}.personal-settings-container .verify-action{cursor:pointer}.personal-settings-container input:disabled{background-color:#fff;color:#000;border:none;opacity:100}.verification-dialog{display:none;right:-9px;top:40px;width:275px}.verification-dialog p{padding:10px}.verification-dialog .verificationCode{font-family:monospace;display:block;overflow-wrap:break-word}.federation-menu{position:relative;cursor:pointer;width:44px;height:44px;padding:10px;margin:0;background:none;border:none}.federation-menu:hover,.federation-menu:focus{background-color:var(--color-background-hover);border-radius:var(--border-radius-pill)}.federation-menu:hover .icon-federation-menu,.federation-menu:focus .icon-federation-menu{opacity:.8}.federation-menu .icon-federation-menu{padding-left:16px;background-size:16px;background-position:left center;opacity:.3;cursor:inherit}.federation-menu .icon-federation-menu .icon-triangle-s{display:inline-block;vertical-align:middle;cursor:inherit}.federation-menu .federationScopeMenu{top:44px}.federation-menu .federationScopeMenu.popovermenu .menuitem{font-size:12.8px;line-height:1.6em}.federation-menu .federationScopeMenu.popovermenu .menuitem .menuitem-text-detail{opacity:.75}.federation-menu .federationScopeMenu.popovermenu .menuitem.active{box-shadow:inset 2px 0 var(--color-primary)}.federation-menu .federationScopeMenu.popovermenu .menuitem.active .menuitem-text{font-weight:bold}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled{opacity:.5;cursor:default}.federation-menu .federationScopeMenu.popovermenu .menuitem.disabled *{cursor:default}.clientsbox img{height:60px}#sslCertificate tr.expired{background-color:rgba(255,0,0,.5)}#sslCertificate td{padding:5px}#displaynameerror,#displaynamechanged{display:none}input#identity{width:20em}#showWizard{display:inline-block}.msg.success{color:#fff;background-color:#47a447;padding:3px}.msg.error{color:#fff;background-color:#d2322d;padding:3px}table.nostyle label{margin-right:2em}table.nostyle td{padding:.2em 0}#security-password #passwordform{display:flex;flex-wrap:wrap;flex-direction:column;gap:1rem}#security-password #passwordform .input-control{display:flex;flex-wrap:wrap;flex-direction:column}#security-password #passwordform .input-control label{margin-bottom:.5rem}#security-password #passwordform #pass1,#security-password #passwordform .personal-show-container{flex-shrink:1;width:300px;min-width:150px}#security-password #passwordform .personal-show-container #pass2{position:relative;top:.5rem}#security-password #passwordform .personal-show-container .personal-show-label{top:34px !important;margin-right:0;margin-top:0 !important;right:3px}#security-password #passwordform #pass2{width:100%}#security-password #passwordform .password-state{display:inline-block}#security-password #passwordform .strengthify-wrapper{position:absolute;left:0;width:100%;border-radius:0 0 2px 2px;margin-top:5px;overflow:hidden;height:3px}#two-factor-auth h3{margin-top:24px}#two-factor-auth li>div{margin-left:20px}#two-factor-auth .two-factor-provider-settings-icon{width:16px;height:16px;vertical-align:sub;filter:var(--background-invert-if-dark)}.isgroup .groupname{width:85%;display:block;overflow:hidden;text-overflow:ellipsis}.isgroup.active .groupname{width:65%}li.active .delete,li.active .rename{display:block}.app-navigation-entry-utils .delete,.app-navigation-entry-utils .rename{display:none}#usersearchform{position:absolute;top:2px;right:0}#usersearchform input{width:150px}#usersearchform label{font-weight:bold}table.grid{width:100%}table.grid th{height:2em;color:#999;border-bottom:1px solid var(--color-border);padding:0 .5em;padding-left:.8em;text-align:left;font-weight:normal}table.grid td{border-bottom:1px solid var(--color-border);padding:0 .5em;padding-left:.8em;text-align:left;font-weight:normal}td.name,th.name{padding-left:.8em;min-width:5em;max-width:12em;text-overflow:ellipsis;overflow:hidden}td.password,th.password{padding-left:.8em}td.password>img,th.password>img{visibility:hidden}td.displayName>img,th.displayName>img{visibility:hidden}td.password,td.mailAddress,th.password,th.mailAddress{min-width:5em;max-width:12em;cursor:pointer}td.password span,td.mailAddress span,th.password span,th.mailAddress span{width:90%;display:inline-block;text-overflow:ellipsis;overflow:hidden}td.mailAddress,th.mailAddress{cursor:pointer}td.password>span,th.password>span{margin-right:1.2em;color:#c7c7c7}span.usersLastLoginTooltip{white-space:nowrap}#app-content>svg.app-filter{float:left;height:0;width:0}#app-category-app-bundles{margin-bottom:20px}.appinfo{margin:1em 40px}#app-navigation img{margin-bottom:-3px;margin-right:6px;width:16px}#app-navigation li span.no-icon{padding-left:32px}#app-navigation ul li.active>span.utils .delete,#app-navigation ul li.active>span.utils .rename{display:block}#app-navigation .appwarning{background:#fcc}#app-navigation.appwarning:hover{background:#fbb}#app-navigation .app-external{color:var(--color-text-maxcontrast)}span.version{margin-left:1em;margin-right:1em;color:var(--color-text-maxcontrast)}.app-version{color:var(--color-text-maxcontrast)}.app-level span{color:var(--color-text-maxcontrast);background-color:rgba(0,0,0,0);border:1px solid var(--color-text-maxcontrast);border-radius:var(--border-radius);padding:3px 6px}.app-level a{padding:10px;margin:-6px;white-space:nowrap}.app-level .official{background-position:left center;background-position:5px center;padding-left:25px}.app-level .supported{border-color:var(--color-success);background-position:left center;background-position:5px center;padding-left:25px;color:var(--color-success)}.app-score{position:relative;top:4px;opacity:.5}.app-settings-content #searchresults{display:none}#apps-list.store .section{border:0}#apps-list.store .app-name{display:block;margin:5px 0}#apps-list.store .app-name,#apps-list.store .app-image *{cursor:pointer}#apps-list.store .app-summary{opacity:.7}#apps-list.store .app-image-icon .icon-settings-dark{width:100%;height:150px;background-size:45px;opacity:.5}#apps-list.store .app-score-image{height:14px}#apps-list.store .actions{margin-top:10px}#app-sidebar #app-details-view h2 .icon-settings-dark,#app-sidebar #app-details-view h2 svg{display:inline-block;width:16px;height:16px;margin-right:10px;opacity:.7}#app-sidebar #app-details-view .app-level{clear:right;width:100%}#app-sidebar #app-details-view .app-level .supported,#app-sidebar #app-details-view .app-level .official{vertical-align:top}#app-sidebar #app-details-view .app-level .app-score-image{float:right}#app-sidebar #app-details-view .app-author,#app-sidebar #app-details-view .app-licence{color:var(--color-text-maxcontrast)}#app-sidebar #app-details-view .app-dependencies{margin:10px 0}#app-sidebar #app-details-view .app-description p{margin:10px 0}#app-sidebar #app-details-view .close{position:absolute;top:0;right:0;padding:14px;opacity:.5;z-index:1;width:44px;height:44px}#app-sidebar #app-details-view .actions{display:flex;align-items:center}#app-sidebar #app-details-view .actions .app-groups{padding:5px}#app-sidebar #app-details-view .appslink{text-decoration:underline;margin-right:5px}#app-sidebar #app-details-view .app-level,#app-sidebar #app-details-view .actions,#app-sidebar #app-details-view .documentation,#app-sidebar #app-details-view .app-dependencies,#app-sidebar #app-details-view .app-description{margin:20px 0}@media only screen and (min-width: 1601px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1600px){.store .section{width:25%}.with-app-sidebar .store .section{width:33%}}@media only screen and (max-width: 1400px){.store .section{width:33%}.with-app-sidebar .store .section{width:50%}}@media only screen and (max-width: 900px){.store .section{width:50%}.with-app-sidebar .store .section{width:100%}}@media only screen and (max-width: 1024px){.store .section{width:50%}}@media only screen and (max-width: 480px){.store .section{width:100%}}@media only screen and (max-width: 900px){.apps-list.installed .app-version,.apps-list.installed .app-level{display:none !important}}@media only screen and (max-width: 500px){.apps-list.installed .app-groups{display:none !important}}.section{margin-bottom:0}.section:not(:last-child){border-bottom:1px solid var(--color-border)}.section h2{margin-bottom:22px}.section h2 .icon-info{padding:6px 20px;vertical-align:text-bottom;display:inline-block}.followupsection{display:block;padding:0 30px 30px 30px}.app-image{position:relative;height:150px;opacity:1;overflow:hidden}.app-name,.app-version,.app-score,.app-level{display:inline-block}.app-description-toggle-show,.app-description-toggle-hide{clear:both;padding:7px 0;cursor:pointer;opacity:.5}.app-description-container{clear:both;position:relative;top:7px}.app-description{clear:both}#app-category-1{margin-bottom:18px}#app-category-925{text-transform:capitalize}.app-dependencies{color:#ce3702}.missing-dependencies{list-style:initial;list-style-type:initial;list-style-position:inside}.apps-list{display:flex;flex-wrap:wrap;align-content:flex-start}.apps-list .section{cursor:pointer}.apps-list .app-list-move{transition:transform 1s}.apps-list #app-list-update-all{margin-left:10px}.apps-list .toolbar{height:60px;padding:8px;padding-left:60px;width:100%;background-color:var(--color-main-background);position:sticky;top:0;z-index:1;display:flex;align-items:center}.apps-list.installed{margin-bottom:100px}.apps-list.installed .apps-list-container{display:table;width:100%;height:auto;margin-top:60px}.apps-list.installed .section{display:table-row;padding:0;margin:0}.apps-list.installed .section>*{display:table-cell;height:initial;vertical-align:middle;float:none;border-bottom:1px solid var(--color-border);padding:6px;box-sizing:border-box}.apps-list.installed .section.selected{background-color:var(--color-background-dark)}.apps-list.installed .groups-enable{margin-top:0}.apps-list.installed .groups-enable label{margin-right:3px}.apps-list.installed .app-image{width:44px;height:auto;text-align:right}.apps-list.installed .app-image-icon svg,.apps-list.installed .app-image-icon .icon-settings-dark{margin-top:5px;width:20px;height:20px;opacity:.5;background-size:cover;display:inline-block}.apps-list.installed .actions{text-align:right}.apps-list.installed .actions .icon-loading-small{display:inline-block;top:4px;margin-right:10px}.apps-list:not(.installed) .app-image-icon svg{position:absolute;bottom:43px;width:64px;height:64px;opacity:.1}.apps-list.hidden{display:none}.apps-list .section{position:relative;flex:0 0 auto}.apps-list .section h2.app-name{display:block;margin:8px 0}.apps-list .section:hover{background-color:var(--color-background-dark)}.apps-list .app-description p{margin:10px 0}.apps-list .app-description ul{list-style:disc}.apps-list .app-description ol{list-style:decimal}.apps-list .app-description ol ol,.apps-list .app-description ol ul{padding-left:15px}.apps-list .app-description>ul,.apps-list .app-description>ol{margin-left:19px}.apps-list .app-description ul ol,.apps-list .app-description ul ul{padding-left:15px}.apps-list .apps-header{display:table-row;position:relative}.apps-list .apps-header div{display:table-cell;height:70px}.apps-list .apps-header h2{display:table-cell;position:absolute;padding-left:6px;padding-top:15px}.apps-list .apps-header h2 .enable{position:relative;top:-1px;margin-left:12px}.apps-list .apps-header h2+.section{margin-top:50px}#apps-list-search .section h2{margin-bottom:0}#log{white-space:normal;margin-bottom:14px}#lessLog{display:none}table.grid td.date{white-space:nowrap}#log-section p{margin-top:20px}#security-warning-state-ok span,#security-warning-state-warning span,#security-warning-state-failure span,#security-warning-state-loading span{vertical-align:middle}#security-warning-state-ok span.message,#security-warning-state-warning span.message,#security-warning-state-failure span.message,#security-warning-state-loading span.message{padding:12px}#security-warning-state-ok span.icon,#security-warning-state-warning span.icon,#security-warning-state-failure span.icon,#security-warning-state-loading span.icon{width:32px;height:32px;background-position:center center;display:inline-block;border-radius:50%}#security-warning-state-ok span.icon-checkmark-white,#security-warning-state-warning span.icon-checkmark-white,#security-warning-state-failure span.icon-checkmark-white,#security-warning-state-loading span.icon-checkmark-white{background-color:var(--color-success)}#security-warning-state-ok span.icon-error-white,#security-warning-state-warning span.icon-error-white,#security-warning-state-failure span.icon-error-white,#security-warning-state-loading span.icon-error-white{background-color:var(--color-warning)}#security-warning-state-ok span.icon-close-white,#security-warning-state-warning span.icon-close-white,#security-warning-state-failure span.icon-close-white,#security-warning-state-loading span.icon-close-white{background-color:var(--color-error)}#shareAPI.loading>div{display:none}#shareAPI p{padding-bottom:.8em}#shareAPI input#shareapiExpireAfterNDays{width:40px}#shareAPI .indent{padding-left:28px}#shareAPI .double-indent{padding-left:56px}#shareAPI .nocheckbox{padding-left:20px}#shareApiDefaultPermissionsSection label{margin-right:20px}#fileSharingSettings h3{display:inline-block}#publicShareDisclaimerText{width:calc(100% - 23px);max-width:600px;height:150px;margin-left:20px;box-sizing:border-box}.icon-info{padding:11px 20px;vertical-align:text-bottom;opacity:.5}#two-factor-auth h2,#shareAPI h2,#mail_general_settings h2{display:inline-block}.mail_settings p label:first-child{display:inline-block;width:300px;text-align:right}.mail_settings p select:nth-child(2),.mail_settings p input:not([type=button]){width:143px}#mail_smtpport{width:60px}.cronlog{margin-left:10px}.status{display:inline-block;height:16px;width:16px;vertical-align:text-bottom}.status.success{border-radius:50%}#selectGroups select{box-sizing:border-box;display:inline-block;height:36px;padding:7px 10px}#log .log-message{word-break:break-all;min-width:180px}span.success{background-color:var(--color-success);border-radius:var(--border-radius)}span.error{background-color:var(--color-error)}span.indeterminate{background-color:var(--color-warning);border-radius:40% 0}doesnotexist:-o-prefocus,.strengthify-wrapper{left:185px;width:129px}.trusted-domain-warning{color:#fff;padding:5px;background:#ce3702;border-radius:5px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}#postsetupchecks ul{margin-left:44px;list-style:disc}#postsetupchecks ul li{margin:10px 0}#postsetupchecks ul ul{list-style:circle}#postsetupchecks .loading{height:50px;background-position:left center}#postsetupchecks .errors,#postsetupchecks .errors a{color:var(--color-error)}#postsetupchecks .warnings,#postsetupchecks .warnings a{color:var(--color-warning)}#postsetupchecks .hint{margin:20px 0}#security-warning a{text-decoration:underline}#security-warning .extra-top-margin{margin-top:12px}#admin-tips li{list-style:initial}#admin-tips li a{display:inline-block;padding:3px 0}#warning{color:red}.settings-hint{margin-top:-12px;margin-bottom:12px;opacity:.7}#body-settings #app-content.user-list-grid{display:grid;grid-column-gap:20px;grid-auto-rows:minmax(60px, max-content)}#body-settings #app-content.user-list-grid .row{display:flex;display:grid;min-height:60px;grid-row-start:span 1;grid-gap:3px;align-items:center;grid-template-columns:44px minmax(190px, 1fr) minmax(160px, 1fr) minmax(160px, 1fr) minmax(240px, 1fr) minmax(240px, 1fr) repeat(auto-fit, minmax(160px, 1fr));border-bottom:var(--color-border) 1px solid}#body-settings #app-content.user-list-grid .row.disabled{opacity:.5}#body-settings #app-content.user-list-grid .row .name,#body-settings #app-content.user-list-grid .row .password,#body-settings #app-content.user-list-grid .row .mailAddress,#body-settings #app-content.user-list-grid .row .languages,#body-settings #app-content.user-list-grid .row .storageLocation,#body-settings #app-content.user-list-grid .row .userBackend,#body-settings #app-content.user-list-grid .row .lastLogin{min-width:160px}#body-settings #app-content.user-list-grid .row .name doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .name .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .password doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .password .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .mailAddress doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .mailAddress .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .languages doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .languages .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .storageLocation doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .storageLocation .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .userBackend doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .userBackend .strengthify-wrapper,#body-settings #app-content.user-list-grid .row .lastLogin doesnotexist:-o-prefocus,#body-settings #app-content.user-list-grid .row .lastLogin .strengthify-wrapper{color:var(--color-text-dark);vertical-align:baseline;text-overflow:ellipsis}#body-settings #app-content.user-list-grid .row:not(.row--editable).name,#body-settings #app-content.user-list-grid .row:not(.row--editable).password,#body-settings #app-content.user-list-grid .row:not(.row--editable).displayName,#body-settings #app-content.user-list-grid .row:not(.row--editable).mailAddress,#body-settings #app-content.user-list-grid .row:not(.row--editable).userBackend,#body-settings #app-content.user-list-grid .row:not(.row--editable).languages{overflow:hidden}#body-settings #app-content.user-list-grid .row:not(.row--editable) .groups,#body-settings #app-content.user-list-grid .row:not(.row--editable) .subadmins{overflow:auto;max-height:100%}#body-settings #app-content.user-list-grid .row .groups,#body-settings #app-content.user-list-grid .row .subadmins,#body-settings #app-content.user-list-grid .row .quota{min-width:160px}#body-settings #app-content.user-list-grid .row .groups .multiselect,#body-settings #app-content.user-list-grid .row .subadmins .multiselect,#body-settings #app-content.user-list-grid .row .quota .multiselect{width:100%;color:var(--color-text-dark);vertical-align:baseline}#body-settings #app-content.user-list-grid .row .groups progress,#body-settings #app-content.user-list-grid .row .subadmins progress,#body-settings #app-content.user-list-grid .row .quota progress{max-width:95%}#body-settings #app-content.user-list-grid .row .obfuscated{width:400px;opacity:.7}#body-settings #app-content.user-list-grid .row .userActions{display:flex;justify-content:flex-end;position:sticky;right:0px;min-width:88px;background-color:var(--color-main-background)}#body-settings #app-content.user-list-grid .row.row--editable .userActions{z-index:10}#body-settings #app-content.user-list-grid .row .subtitle{color:var(--color-text-maxcontrast);vertical-align:baseline}#body-settings #app-content.user-list-grid .row#grid-header{position:sticky;align-self:normal;background-color:var(--color-main-background);z-index:100;top:0}#body-settings #app-content.user-list-grid .row#grid-header.sticky{box-shadow:0 -2px 10px 1px var(--color-box-shadow)}#body-settings #app-content.user-list-grid .row#grid-header{color:var(--color-text-maxcontrast);border-bottom-width:thin}#body-settings #app-content.user-list-grid .row#grid-header #headerDisplayName,#body-settings #app-content.user-list-grid .row#grid-header #headerPassword,#body-settings #app-content.user-list-grid .row#grid-header #headerAddress,#body-settings #app-content.user-list-grid .row#grid-header #headerGroups,#body-settings #app-content.user-list-grid .row#grid-header #headerSubAdmins,#body-settings #app-content.user-list-grid .row#grid-header #theHeaderUserBackend,#body-settings #app-content.user-list-grid .row#grid-header #theHeaderLastLogin,#body-settings #app-content.user-list-grid .row#grid-header #headerQuota,#body-settings #app-content.user-list-grid .row#grid-header #theHeaderStorageLocation,#body-settings #app-content.user-list-grid .row#grid-header #headerLanguages{padding-left:7px;text-transform:none;color:var(--color-text-maxcontrast);vertical-align:baseline}#body-settings #app-content.user-list-grid .row:hover input:not([type=submit]):not(:focus):not(:active){border-color:var(--color-border) !important}#body-settings #app-content.user-list-grid .row:hover:not(#grid-header){box-shadow:5px 0 0 var(--color-primary-element) inset}#body-settings #app-content.user-list-grid .row>form{width:100%}#body-settings #app-content.user-list-grid .row>div,#body-settings #app-content.user-list-grid .row>.displayName>form,#body-settings #app-content.user-list-grid .row>form{grid-row:1;display:inline-flex;color:var(--color-text-lighter);flex-grow:1}#body-settings #app-content.user-list-grid .row>div>input:not(:focus):not(:active),#body-settings #app-content.user-list-grid .row>.displayName>form>input:not(:focus):not(:active),#body-settings #app-content.user-list-grid .row>form>input:not(:focus):not(:active){border-color:rgba(0,0,0,0);cursor:pointer}#body-settings #app-content.user-list-grid .row>div>input:focus+.icon-confirm,#body-settings #app-content.user-list-grid .row>div>input:active+.icon-confirm,#body-settings #app-content.user-list-grid .row>.displayName>form>input:focus+.icon-confirm,#body-settings #app-content.user-list-grid .row>.displayName>form>input:active+.icon-confirm,#body-settings #app-content.user-list-grid .row>form>input:focus+.icon-confirm,#body-settings #app-content.user-list-grid .row>form>input:active+.icon-confirm{display:block !important}#body-settings #app-content.user-list-grid .row>div:not(.userActions)>input:not([type=submit]),#body-settings #app-content.user-list-grid .row>.displayName>form:not(.userActions)>input:not([type=submit]),#body-settings #app-content.user-list-grid .row>form:not(.userActions)>input:not([type=submit]){width:100%;min-width:0}#body-settings #app-content.user-list-grid .row>div.name,#body-settings #app-content.user-list-grid .row>.displayName>form.name,#body-settings #app-content.user-list-grid .row>form.name{word-break:break-all}#body-settings #app-content.user-list-grid .row>div.displayName>input,#body-settings #app-content.user-list-grid .row>div.mailAddress>input,#body-settings #app-content.user-list-grid .row>.displayName>form.displayName>input,#body-settings #app-content.user-list-grid .row>.displayName>form.mailAddress>input,#body-settings #app-content.user-list-grid .row>form.displayName>input,#body-settings #app-content.user-list-grid .row>form.mailAddress>input{text-overflow:ellipsis;flex-grow:1}#body-settings #app-content.user-list-grid .row>div.name,#body-settings #app-content.user-list-grid .row>div.userBackend,#body-settings #app-content.user-list-grid .row>.displayName>form.name,#body-settings #app-content.user-list-grid .row>.displayName>form.userBackend,#body-settings #app-content.user-list-grid .row>form.name,#body-settings #app-content.user-list-grid .row>form.userBackend{line-height:1.3em;max-height:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}#body-settings #app-content.user-list-grid .row>div.name .subtitle,#body-settings #app-content.user-list-grid .row>.displayName>form.name .subtitle,#body-settings #app-content.user-list-grid .row>form.name .subtitle{color:var(--color-main-text)}#body-settings #app-content.user-list-grid .row>div.quota,#body-settings #app-content.user-list-grid .row>.displayName>form.quota,#body-settings #app-content.user-list-grid .row>form.quota{display:flex;justify-content:left;white-space:nowrap;position:relative}#body-settings #app-content.user-list-grid .row>div.quota progress,#body-settings #app-content.user-list-grid .row>.displayName>form.quota progress,#body-settings #app-content.user-list-grid .row>form.quota progress{width:150px;margin-top:35px;height:3px}#body-settings #app-content.user-list-grid .row>div .icon-confirm,#body-settings #app-content.user-list-grid .row>.displayName>form .icon-confirm,#body-settings #app-content.user-list-grid .row>form .icon-confirm{flex:0 0 auto;cursor:pointer}#body-settings #app-content.user-list-grid .row>div .icon-confirm:not(:active),#body-settings #app-content.user-list-grid .row>.displayName>form .icon-confirm:not(:active),#body-settings #app-content.user-list-grid .row>form .icon-confirm:not(:active){display:none}#body-settings #app-content.user-list-grid .row>div.avatar,#body-settings #app-content.user-list-grid .row>.displayName>form.avatar,#body-settings #app-content.user-list-grid .row>form.avatar{height:32px;width:32px;margin:6px}#body-settings #app-content.user-list-grid .row>div.avatar img,#body-settings #app-content.user-list-grid .row>.displayName>form.avatar img,#body-settings #app-content.user-list-grid .row>form.avatar img{display:block}#body-settings #app-content.user-list-grid .row>div.userActions,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions,#body-settings #app-content.user-list-grid .row>form.userActions{display:flex;justify-content:flex-end}#body-settings #app-content.user-list-grid .row>div.userActions #newsubmit,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions #newsubmit,#body-settings #app-content.user-list-grid .row>form.userActions #newsubmit{width:100%}#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions{position:relative;display:flex;align-items:center;background-color:var(--color-main-background)}#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more{width:44px;height:44px;opacity:.5;cursor:pointer}#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more:focus,#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more:hover,#body-settings #app-content.user-list-grid .row>div.userActions .toggleUserActions .icon-more:active,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more:focus,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more:hover,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .toggleUserActions .icon-more:active,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more:focus,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more:hover,#body-settings #app-content.user-list-grid .row>form.userActions .toggleUserActions .icon-more:active{opacity:.7;background-color:var(--color-background-dark)}#body-settings #app-content.user-list-grid .row>div.userActions .feedback,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .feedback,#body-settings #app-content.user-list-grid .row>form.userActions .feedback{display:flex;align-items:center;white-space:nowrap;transition:opacity 200ms ease-in-out}#body-settings #app-content.user-list-grid .row>div.userActions .feedback .icon-checkmark,#body-settings #app-content.user-list-grid .row>.displayName>form.userActions .feedback .icon-checkmark,#body-settings #app-content.user-list-grid .row>form.userActions .feedback .icon-checkmark{opacity:.5;margin-right:5px}#body-settings #app-content.user-list-grid .row>div .multiselect.multiselect-vue,#body-settings #app-content.user-list-grid .row>.displayName>form .multiselect.multiselect-vue,#body-settings #app-content.user-list-grid .row>form .multiselect.multiselect-vue{min-width:100%;width:100%}#body-settings #app-content.user-list-grid .infinite-loading-container{display:flex;align-items:center;justify-content:center;grid-row-start:span 4}#body-settings #app-content.user-list-grid .users-list-end{opacity:.5;user-select:none}.animated{animation:blink-animation 1s steps(5, start) 4}@keyframes blink-animation{to{opacity:.6}}@-webkit-keyframes blink-animation{to{opacity:1}}/*# sourceMappingURL=settings.css.map */ diff --git a/apps/settings/css/settings.css.map b/apps/settings/css/settings.css.map index 0872e074c7c..6bf7226405a 100644 --- a/apps/settings/css/settings.css.map +++ b/apps/settings/css/settings.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["settings.scss","../../../core/css/functions.scss"],"names":[],"mappings":"AAOC,0BACC,WAKF,OACC,WAID,4BC+CC,2CD3CD,mBC2CC,kDDvCD,qBCuCC,yCDnCD,0BCmCC,wCD/BD,oEC+BC,2CD3BD,oCACC,oBACA,0BACA,+BACA,mBAGD,4BACC,oBACA,kCAGD,yBACC,WAIA,wCACC,kBACA,yDACC,gBAIA,mOACC,WAKH,uCACC,aAGD,sCACC,WAED,uDACC,WAKD,gBACC,WAIF,mBACC,aACA,aACA,iBACA,4DACA,qBAEA,4BACC,kBACA,SAEA,+BACC,mBAIA,qCACC,iBAKH,kCACC,iBACA,mBACA,gBAGD,mGACC,4BACA,kBACA,WAMF,oBACC,kBACA,wCACC,0BACA,8CACC,oDAKH,aACC,qBACA,YACA,kBACA,sCACA,WACA,wCACA,sCACA,6CAEA,0DAGC,2BACA,2CACA,6CAGD,uBACC,kBACA,yBAIF,6BACC,oBACA,kCAEA,mCACC,WAIA,oCACC,kBACA,oBACA,iBACA,2BACA,WACA,mBACA,QAEA,0CACC,mBACA,uBACA,gBAKD,gIACC,kBACA,UACA,UACA,oBACA,YAKH,qCACC,kBACA,UACA,MACA,SAEA,yCACC,qBAIF,4CACC,eAGD,4CACC,sBACA,WACA,YACA,YAMF,qBACC,aACA,WACA,SACA,YAEA,uBACC,aAGD,uCACC,sBACA,cACA,yBAIF,iBACC,kBACA,eACA,WACA,YACA,aACA,SACA,gBACA,YAEA,8CAEC,+CACA,wCAEA,0FACC,WAIF,uCACC,kBACA,qBACA,gCACA,WACA,eAEA,wDACC,qBACA,sBACA,eAIF,sCACC,SAGC,4DAEC,iBACA,kBAEA,kFACC,YAGD,mEACC,4CAEA,kFACC,iBAIF,qEACC,WAEA,eAEA,uEACC,eAQN,gBACC,YAIA,2BACC,kCAGD,mBACC,YAIF,sCAEC,aAGD,eACC,WAGD,YACC,qBAIA,aACC,WACA,yBACA,YAGD,WACC,WACA,yBACA,YAMD,oBACC,iBAGD,iBACC,eAKD,iCACC,aACA,eACA,sBACA,SACA,gDACC,aACA,eACA,sBACA,sDACC,oBAIF,kGACC,cACA,YACA,gBAKA,iEACC,kBACA,UAED,+EACC,oBACA,eACA,wBACA,UAIF,wCACC,WAGD,iDACC,qBAGD,sDACC,kBACA,OACA,WACA,0BACA,eACA,gBACA,WAQF,oBACC,gBAGD,wBACC,iBAGD,oDACC,WACA,YACA,mBACA,wCAOD,oBACC,UACA,cACA,gBACA,uBAGD,2BACC,UAKD,oCAEC,cAKD,wEAEC,aAIF,gBACC,kBACA,QACA,QAEA,sBACC,YAGD,sBACC,iBAKF,WACC,WAEA,cACC,WACA,WACA,4CACA,eACA,kBACA,gBACA,mBAGD,cACC,4CACA,eACA,kBACA,gBACA,mBAKD,gBACC,kBACA,cACA,eACA,uBACA,gBAGD,wBACC,kBAEA,gCACC,kBAIF,sCACC,kBAGD,sDAEC,cACA,eACA,eAEA,0EACC,UACA,qBACA,uBACA,gBAIF,8BACC,eAGD,kCACC,mBACA,cAIF,2BACC,mBAID,4BACC,WACA,SACA,QAGD,0BACC,mBAGD,SACC,gBAKA,oBACC,mBACA,iBACA,WAGD,gCACC,kBAIA,gGACC,cAIF,4BACC,gBAGD,iCACC,gBAGD,8BACC,oCAIF,aACC,gBACA,iBACA,oCAGD,aACC,oCAIA,gBACC,oCACA,+BACA,+CACA,mCACA,gBAGD,aACC,aACA,YACA,mBAGD,qBACC,gCACA,+BACA,kBAGD,sBACC,kCACA,gCACA,+BACA,kBACA,2BAIF,WACC,kBACA,QACA,WAIA,qCACC,aAMD,0BACC,SAGD,2BACC,cACA,aAGD,yDACC,eAGD,8BACC,WAGD,qDACC,WACA,aACA,qBACA,WAGD,kCACC,YAGD,0BACC,gBAMA,4FAEC,qBACA,WACA,YACA,kBACA,WAIF,0CACC,YACA,WAEA,yGAEC,mBAGD,2DACC,YAIF,uFACC,oCAGD,iDACC,cAGD,kDACC,cAGD,sCACC,kBACA,MACA,QACA,aACA,WACA,UACA,WACA,YAGD,wCACC,aACA,mBAEA,oDACC,YAIF,yCACC,0BACA,iBAGD,iOAKC,cAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,0CACC,gBACC,UAED,kCACC,YAIF,2CACC,gBACC,WAIF,0CACC,gBACC,YAKF,0CAEE,kEACC,yBAKH,0CACC,iCACC,yBAIF,SACC,gBAEA,0BACC,4CAID,YACC,mBAEA,uBACC,iBACA,2BACA,qBAKH,iBACC,cACA,yBAGD,WACC,kBACA,aACA,UACA,gBAGD,6CACC,qBAGD,0DACC,WACA,cACA,eACA,WAGD,2BACC,WACA,kBACA,QAGD,iBACC,WAGD,gBACC,mBAKD,kBACC,0BAGD,kBACC,cAGD,sBACC,mBACA,wBACA,2BAGD,WAyGC,aACA,eACA,yBAvGA,oBACC,eAGD,0BACC,wBAGD,gCACC,iBAGD,oBACC,OAfgB,KAgBhB,QAjBiB,IAmBjB,aAlBgB,KAmBhB,WACA,8CACA,gBACA,MACA,UACA,aACA,mBAGD,qBAQC,oBAPA,0CACC,cACA,WACA,YACA,WAjCe,KAsChB,8BACC,kBACA,UACA,SAEA,gCACC,mBACA,eACA,sBACA,WACA,4CACA,YACA,sBAGD,uCACC,8CAKF,oCACC,aAEA,0CACC,iBAIF,gCACC,WACA,YACA,iBAGD,kGAEC,eACA,WACA,YACA,WACA,sBACA,qBAGD,8BACC,iBAEA,kDACC,qBACA,QACA,kBAKH,+CACC,kBACA,YAEA,WACA,YACA,WAOD,kBACC,aAGD,oBACC,kBACA,cAEA,gCACC,cACA,aAGD,0BACC,8CAKD,8BACC,cAGD,+BACC,gBAGD,+BACC,mBAEA,oEACC,kBAKD,8DACC,iBAKD,oEACC,kBAMH,wBACC,kBACA,kBAEA,4BACC,mBACA,YAGD,2BACC,mBACA,kBACA,iBACA,iBAEA,mCACC,kBACA,SACA,iBAGD,oCACC,gBAQF,8BACC,gBAMH,KACC,mBACA,mBAGD,SACC,aAGD,mBACC,mBAGD,eACC,gBAOA,+IACC,sBAEA,+KACC,aAGD,mKACC,WACA,YACA,kCACA,qBACA,kBAGD,mOACC,sCAGD,mNACC,sCAGD,mNACC,oCAMF,YACC,oBAGD,yCACC,WAGD,kBACC,kBAGD,yBACC,kBAGD,sBACC,kBAIF,yCACC,kBAGD,wBACC,qBAGD,2BACC,wBAEA,gBACA,aACA,iBACA,sBAKD,WACC,kBACA,2BACA,WAGD,2DAGC,qBAIA,mCACC,qBACA,YACA,iBAGD,+EAEC,YAIF,eACC,WAGD,SACC,iBAGD,QACC,qBACA,YACA,WACA,2BAEA,gBACC,kBAIF,qBACC,sBACA,qBACA,YACA,iBAGD,kBACC,qBACA,gBAIA,aACC,sCACA,mCAGD,WACC,oCAGD,mBACC,sCACA,oBAMF,8CACC,WACA,YAGD,wBACC,WACA,YACA,mBACA,kBACA,+DAIA,oBACC,iBACA,gBAEA,uBACC,cAGD,uBACC,kBAIF,0BACC,YACA,gCAGD,oDACC,yBAGD,wDACC,2BAGD,uBACC,cAKD,oBACC,0BAGD,oCACC,gBAIF,eACC,mBAEA,iBACC,qBACA,cAIF,SACC,UAGD,eACC,iBACA,mBACA,WASA,2CACC,aACA,qBACA,yCAEA,gDAGC,aACA,aACA,WAbgB,KAchB,sBACA,aACA,mBAGA,sBACE,yIAOF,4CAEA,yDACC,WAID,iaAOC,UAxCkB,MA0ClB,ooCACC,6BACA,wBACA,uBAKD,odAMC,gBAMD,2JAEC,cACA,gBAIF,0KAGC,UAxEkB,MA0ElB,iNACC,WACA,6BACA,wBAGD,qMACC,cAIF,4DACC,YACA,WAGD,6DACC,aACA,yBACA,gBACA,UACA,eACA,8CAGD,2EACC,WAGD,0DACC,oCACA,wBAID,4DACC,gBACA,kBACA,8CACA,YACA,MAEA,mEACC,mDAIF,4DACC,oCACA,yBAEA,2wBAWC,iBACA,oBACA,oCACA,wBAKD,wGACC,4CAGD,wEACC,sDAIF,qDACC,WAGD,2KAGC,WACA,oBACA,gCACA,YAEA,wQACC,2BACA,eAIA,qfACC,yBAKF,4SACC,WACA,YAGD,0LACC,qBAKA,kcACC,uBACA,YAIF,yYAGC,kBACA,gBACA,gBAIA,uBACA,oBACA,qBACA,4BAGD,wNACC,6BAGD,6LACC,aACA,qBACA,mBACA,kBAEA,wNACC,YACA,gBACA,WAIF,qNACC,cACA,eAEA,4PACC,aAIF,gMACC,YACA,WACA,WAEA,4MACC,cAIF,+MACC,aACA,yBAEA,gPACC,WAGD,wQACC,kBACA,aACA,mBACA,8CAEA,ySACC,WACA,YACA,WACA,eAEA,o7BAGC,WACA,8CAKH,6OACC,aACA,mBACA,mBACA,qCAEA,6RACC,WACA,iBAMH,kQACC,eACA,WAKH,uEACC,aACA,mBACA,uBACA,sBAGD,2DACC,WACA,iBAKH,UACI,+CAGJ,2BACE,GACE,YAGJ,mCACE,GACE","file":"settings.css"}
\ No newline at end of file +{"version":3,"sourceRoot":"","sources":["settings.scss","../../../core/css/functions.scss"],"names":[],"mappings":"AAOC,0BACC,WAKF,OACC,WAID,4BC+CC,2CD3CD,mBC2CC,kDDvCD,qBCuCC,yCDnCD,0BCmCC,wCD/BD,oEC+BC,2CD3BD,oCACC,oBACA,0BACA,+BACA,mBAGD,4BACC,oBACA,kCAGD,yBACC,WAIA,wCACC,kBACA,yDACC,gBAIA,mOACC,WAKH,uCACC,aAGD,sCACC,WAED,uDACC,WAKD,gBACC,WAIF,mBACC,aACA,aACA,iBACA,4DACA,qBAEA,4BACC,kBACA,SAEA,+BACC,mBAIA,qCACC,iBAKH,kCACC,iBACA,mBACA,gBAGD,mGACC,4BACA,kBACA,WAMF,oBACC,kBACA,wCACC,0BACA,8CACC,oDAKH,aACC,qBACA,YACA,kBACA,sCACA,WACA,wCACA,sCACA,6CAEA,0DAGC,2BACA,2CACA,6CAGD,uBACC,kBACA,yBAIF,6BACC,oBACA,kCAEA,mCACC,WAIA,oCACC,kBACA,oBACA,iBACA,2BACA,WACA,mBACA,QAEA,0CACC,mBACA,uBACA,gBAKD,gIACC,kBACA,UACA,UACA,oBACA,YAKH,qCACC,kBACA,UACA,MACA,SAEA,yCACC,qBAIF,4CACC,eAGD,4CACC,sBACA,WACA,YACA,YAMF,qBACC,aACA,WACA,SACA,YAEA,uBACC,aAGD,uCACC,sBACA,cACA,yBAIF,iBACC,kBACA,eACA,WACA,YACA,aACA,SACA,gBACA,YAEA,8CAEC,+CACA,wCAEA,0FACC,WAIF,uCACC,kBACA,qBACA,gCACA,WACA,eAEA,wDACC,qBACA,sBACA,eAIF,sCACC,SAGC,4DAEC,iBACA,kBAEA,kFACC,YAGD,mEACC,4CAEA,kFACC,iBAIF,qEACC,WAEA,eAEA,uEACC,eAQN,gBACC,YAIA,2BACC,kCAGD,mBACC,YAIF,sCAEC,aAGD,eACC,WAGD,YACC,qBAIA,aACC,WACA,yBACA,YAGD,WACC,WACA,yBACA,YAMD,oBACC,iBAGD,iBACC,eAKD,iCACC,aACA,eACA,sBACA,SACA,gDACC,aACA,eACA,sBACA,sDACC,oBAIF,kGACC,cACA,YACA,gBAKA,iEACC,kBACA,UAED,+EACC,oBACA,eACA,wBACA,UAIF,wCACC,WAGD,iDACC,qBAGD,sDACC,kBACA,OACA,WACA,0BACA,eACA,gBACA,WAQF,oBACC,gBAGD,wBACC,iBAGD,oDACC,WACA,YACA,mBACA,wCAOD,oBACC,UACA,cACA,gBACA,uBAGD,2BACC,UAKD,oCAEC,cAKD,wEAEC,aAIF,gBACC,kBACA,QACA,QAEA,sBACC,YAGD,sBACC,iBAKF,WACC,WAEA,cACC,WACA,WACA,4CACA,eACA,kBACA,gBACA,mBAGD,cACC,4CACA,eACA,kBACA,gBACA,mBAKD,gBACC,kBACA,cACA,eACA,uBACA,gBAGD,wBACC,kBAEA,gCACC,kBAIF,sCACC,kBAGD,sDAEC,cACA,eACA,eAEA,0EACC,UACA,qBACA,uBACA,gBAIF,8BACC,eAGD,kCACC,mBACA,cAIF,2BACC,mBAID,4BACC,WACA,SACA,QAGD,0BACC,mBAGD,SACC,gBAKA,oBACC,mBACA,iBACA,WAGD,gCACC,kBAIA,gGACC,cAIF,4BACC,gBAGD,iCACC,gBAGD,8BACC,oCAIF,aACC,gBACA,iBACA,oCAGD,aACC,oCAIA,gBACC,oCACA,+BACA,+CACA,mCACA,gBAGD,aACC,aACA,YACA,mBAGD,qBACC,gCACA,+BACA,kBAGD,sBACC,kCACA,gCACA,+BACA,kBACA,2BAIF,WACC,kBACA,QACA,WAIA,qCACC,aAMD,0BACC,SAGD,2BACC,cACA,aAGD,yDACC,eAGD,8BACC,WAGD,qDACC,WACA,aACA,qBACA,WAGD,kCACC,YAGD,0BACC,gBAMA,4FAEC,qBACA,WACA,YACA,kBACA,WAIF,0CACC,YACA,WAEA,yGAEC,mBAGD,2DACC,YAIF,uFACC,oCAGD,iDACC,cAGD,kDACC,cAGD,sCACC,kBACA,MACA,QACA,aACA,WACA,UACA,WACA,YAGD,wCACC,aACA,mBAEA,oDACC,YAIF,yCACC,0BACA,iBAGD,iOAKC,cAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,2CACC,gBACC,UAED,kCACC,WAIF,0CACC,gBACC,UAED,kCACC,YAIF,2CACC,gBACC,WAIF,0CACC,gBACC,YAKF,0CAEE,kEACC,yBAKH,0CACC,iCACC,yBAIF,SACC,gBAEA,0BACC,4CAID,YACC,mBAEA,uBACC,iBACA,2BACA,qBAKH,iBACC,cACA,yBAGD,WACC,kBACA,aACA,UACA,gBAGD,6CACC,qBAGD,0DACC,WACA,cACA,eACA,WAGD,2BACC,WACA,kBACA,QAGD,iBACC,WAGD,gBACC,mBAKD,kBACC,0BAGD,kBACC,cAGD,sBACC,mBACA,wBACA,2BAGD,WAyGC,aACA,eACA,yBAvGA,oBACC,eAGD,0BACC,wBAGD,gCACC,iBAGD,oBACC,OAfgB,KAgBhB,QAjBiB,IAmBjB,aAlBgB,KAmBhB,WACA,8CACA,gBACA,MACA,UACA,aACA,mBAGD,qBAQC,oBAPA,0CACC,cACA,WACA,YACA,WAjCe,KAsChB,8BACC,kBACA,UACA,SAEA,gCACC,mBACA,eACA,sBACA,WACA,4CACA,YACA,sBAGD,uCACC,8CAKF,oCACC,aAEA,0CACC,iBAIF,gCACC,WACA,YACA,iBAGD,kGAEC,eACA,WACA,YACA,WACA,sBACA,qBAGD,8BACC,iBAEA,kDACC,qBACA,QACA,kBAKH,+CACC,kBACA,YAEA,WACA,YACA,WAOD,kBACC,aAGD,oBACC,kBACA,cAEA,gCACC,cACA,aAGD,0BACC,8CAKD,8BACC,cAGD,+BACC,gBAGD,+BACC,mBAEA,oEACC,kBAKD,8DACC,iBAKD,oEACC,kBAMH,wBACC,kBACA,kBAEA,4BACC,mBACA,YAGD,2BACC,mBACA,kBACA,iBACA,iBAEA,mCACC,kBACA,SACA,iBAGD,oCACC,gBAQF,8BACC,gBAMH,KACC,mBACA,mBAGD,SACC,aAGD,mBACC,mBAGD,eACC,gBAOA,+IACC,sBAEA,+KACC,aAGD,mKACC,WACA,YACA,kCACA,qBACA,kBAGD,mOACC,sCAGD,mNACC,sCAGD,mNACC,oCAMF,sBACC,aAGD,YACC,oBAGD,yCACC,WAGD,kBACC,kBAGD,yBACC,kBAGD,sBACC,kBAIF,yCACC,kBAGD,wBACC,qBAGD,2BACC,wBAEA,gBACA,aACA,iBACA,sBAKD,WACC,kBACA,2BACA,WAGD,2DAGC,qBAIA,mCACC,qBACA,YACA,iBAGD,+EAEC,YAIF,eACC,WAGD,SACC,iBAGD,QACC,qBACA,YACA,WACA,2BAEA,gBACC,kBAIF,qBACC,sBACA,qBACA,YACA,iBAGD,kBACC,qBACA,gBAIA,aACC,sCACA,mCAGD,WACC,oCAGD,mBACC,sCACA,oBAMF,8CACC,WACA,YAGD,wBACC,WACA,YACA,mBACA,kBACA,+DAIA,oBACC,iBACA,gBAEA,uBACC,cAGD,uBACC,kBAIF,0BACC,YACA,gCAGD,oDACC,yBAGD,wDACC,2BAGD,uBACC,cAKD,oBACC,0BAGD,oCACC,gBAIF,eACC,mBAEA,iBACC,qBACA,cAIF,SACC,UAGD,eACC,iBACA,mBACA,WASA,2CACC,aACA,qBACA,yCAEA,gDAGC,aACA,aACA,WAbgB,KAchB,sBACA,aACA,mBAGA,sBACE,yIAOF,4CAEA,yDACC,WAID,iaAOC,UAxCkB,MA0ClB,ooCACC,6BACA,wBACA,uBAKD,odAMC,gBAMD,2JAEC,cACA,gBAIF,0KAGC,UAxEkB,MA0ElB,iNACC,WACA,6BACA,wBAGD,qMACC,cAIF,4DACC,YACA,WAGD,6DACC,aACA,yBACA,gBACA,UACA,eACA,8CAGD,2EACC,WAGD,0DACC,oCACA,wBAID,4DACC,gBACA,kBACA,8CACA,YACA,MAEA,mEACC,mDAIF,4DACC,oCACA,yBAEA,2wBAWC,iBACA,oBACA,oCACA,wBAKD,wGACC,4CAGD,wEACC,sDAIF,qDACC,WAGD,2KAGC,WACA,oBACA,gCACA,YAEA,wQACC,2BACA,eAIA,qfACC,yBAKF,4SACC,WACA,YAGD,0LACC,qBAKA,kcACC,uBACA,YAIF,yYAGC,kBACA,gBACA,gBAIA,uBACA,oBACA,qBACA,4BAGD,wNACC,6BAGD,6LACC,aACA,qBACA,mBACA,kBAEA,wNACC,YACA,gBACA,WAIF,qNACC,cACA,eAEA,4PACC,aAIF,gMACC,YACA,WACA,WAEA,4MACC,cAIF,+MACC,aACA,yBAEA,gPACC,WAGD,wQACC,kBACA,aACA,mBACA,8CAEA,ySACC,WACA,YACA,WACA,eAEA,o7BAGC,WACA,8CAKH,6OACC,aACA,mBACA,mBACA,qCAEA,6RACC,WACA,iBAMH,kQACC,eACA,WAKH,uEACC,aACA,mBACA,uBACA,sBAGD,2DACC,WACA,iBAKH,UACI,+CAGJ,2BACE,GACE,YAGJ,mCACE,GACE","file":"settings.css"}
\ No newline at end of file diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss index ff5a8df1e69..cd34683c284 100644 --- a/apps/settings/css/settings.scss +++ b/apps/settings/css/settings.scss @@ -1129,6 +1129,10 @@ table.grid td.date { } #shareAPI { + &.loading > div { + display: none; + } + p { padding-bottom: 0.8em; } diff --git a/apps/settings/l10n/sr.js b/apps/settings/l10n/sr.js index 893626fe6aa..5a875feae2d 100644 --- a/apps/settings/l10n/sr.js +++ b/apps/settings/l10n/sr.js @@ -231,12 +231,12 @@ OC.L10N.register( "Copied!" : "Копирано!", "Copy" : "Копирај", "Could not copy app password. Please copy it manually." : "Не могу да копирам апликативну лозинку. Копирајте је ручно.", - "For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information." : "Да би сервер исправно радио, важно је да се правилно подесе позадински послови. Cron је препоручено подешавање. За још информација, молимо вас да погледате документацију .", + "For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information." : "Да би сервер исправно радио, важно је да се правилно подесе позадински послови. Cron је препоручено подешавање. За још информација, молимо вас да погледате документацију.", "Last job execution ran {time}. Something seems wrong." : "Последњи посао се извршавао {time}. Изгледа да нешто није у реду.", - "Last job ran {relativeTime}." : "Последњи посао се изврашавао {relativeTime}.", + "Last job ran {relativeTime}." : "Последњи посао се извршавао {relativeTime}.", "Background job did not run yet!" : "Позадински посао се још увек није покренуо!", "AJAX" : "AJAX", - "Execute one task with each page loaded. Use case: Single user instance." : "Извршава један задатак за сваку учитану страницу. Случај употребе: инстанца са једним корисником", + "Execute one task with each page loaded. Use case: Single user instance." : "Извршава један задатак за сваку учитану страницу. Случај употребе: инстанца са једним корисником.", "Webcron" : "Webcron", "cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (1–5 users depending on the usage)." : "cron.php се регуструје у webcron сервису тако да се cron.php позива преко HTTP сваких 5 минута. Случај употребе: врло мала инстанца (1–5 корисника, у зависности од употребе).", "Cron (Recommended)" : "Cron (Препоручено)", @@ -441,7 +441,7 @@ OC.L10N.register( "Login" : "Пријава", "SSL/TLS" : "SSL/TLS", "Open documentation" : "Отвори документацију", - "It is important to set up this server to be able to send emails, like for password reset and notifications." : "Важно је да подесите сервер да може да шаље е-пошту, када нпр. треба послати ресетовање лозинке или нека обавештења.", + "It is important to set up this server to be able to send emails, like for password reset and notifications." : "Важно је да подесите сервер тако да може да шаље е-пошту када нпр. треба послати ресетовање лозинке или нека обавештења.", "Send mode" : "Режим слања", "Encryption" : "Шифровање", "Sendmail mode" : "Sendmail режим", diff --git a/apps/settings/l10n/sr.json b/apps/settings/l10n/sr.json index ddc8fa1d262..0efdb13fc6a 100644 --- a/apps/settings/l10n/sr.json +++ b/apps/settings/l10n/sr.json @@ -229,12 +229,12 @@ "Copied!" : "Копирано!", "Copy" : "Копирај", "Could not copy app password. Please copy it manually." : "Не могу да копирам апликативну лозинку. Копирајте је ручно.", - "For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information." : "Да би сервер исправно радио, важно је да се правилно подесе позадински послови. Cron је препоручено подешавање. За још информација, молимо вас да погледате документацију .", + "For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information." : "Да би сервер исправно радио, важно је да се правилно подесе позадински послови. Cron је препоручено подешавање. За још информација, молимо вас да погледате документацију.", "Last job execution ran {time}. Something seems wrong." : "Последњи посао се извршавао {time}. Изгледа да нешто није у реду.", - "Last job ran {relativeTime}." : "Последњи посао се изврашавао {relativeTime}.", + "Last job ran {relativeTime}." : "Последњи посао се извршавао {relativeTime}.", "Background job did not run yet!" : "Позадински посао се још увек није покренуо!", "AJAX" : "AJAX", - "Execute one task with each page loaded. Use case: Single user instance." : "Извршава један задатак за сваку учитану страницу. Случај употребе: инстанца са једним корисником", + "Execute one task with each page loaded. Use case: Single user instance." : "Извршава један задатак за сваку учитану страницу. Случај употребе: инстанца са једним корисником.", "Webcron" : "Webcron", "cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (1–5 users depending on the usage)." : "cron.php се регуструје у webcron сервису тако да се cron.php позива преко HTTP сваких 5 минута. Случај употребе: врло мала инстанца (1–5 корисника, у зависности од употребе).", "Cron (Recommended)" : "Cron (Препоручено)", @@ -439,7 +439,7 @@ "Login" : "Пријава", "SSL/TLS" : "SSL/TLS", "Open documentation" : "Отвори документацију", - "It is important to set up this server to be able to send emails, like for password reset and notifications." : "Важно је да подесите сервер да може да шаље е-пошту, када нпр. треба послати ресетовање лозинке или нека обавештења.", + "It is important to set up this server to be able to send emails, like for password reset and notifications." : "Важно је да подесите сервер тако да може да шаље е-пошту када нпр. треба послати ресетовање лозинке или нека обавештења.", "Send mode" : "Режим слања", "Encryption" : "Шифровање", "Sendmail mode" : "Sendmail режим", diff --git a/apps/settings/lib/UserMigration/AccountMigrator.php b/apps/settings/lib/UserMigration/AccountMigrator.php index e8c70624224..a779ad76c8d 100644 --- a/apps/settings/lib/UserMigration/AccountMigrator.php +++ b/apps/settings/lib/UserMigration/AccountMigrator.php @@ -84,7 +84,7 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { /** * {@inheritDoc} */ - public function getEstimatedExportSize(IUser $user): int { + public function getEstimatedExportSize(IUser $user): int|float { $size = 100; // 100KiB for account JSON try { @@ -97,7 +97,7 @@ class AccountMigrator implements IMigrator, ISizeEstimationMigrator { // Skip avatar in size estimate on failure } - return (int)ceil($size); + return ceil($size); } /** diff --git a/apps/settings/src/admin.js b/apps/settings/src/admin.js index e7220df3c91..8bdfa1d0770 100644 --- a/apps/settings/src/admin.js +++ b/apps/settings/src/admin.js @@ -286,4 +286,6 @@ window.addEventListener('DOMContentLoaded', () => { if (document.getElementById('security-warning') !== null) { setupChecks() } + + $('#shareAPI').removeClass('loading') }) diff --git a/apps/settings/src/components/UserList/UserRow.vue b/apps/settings/src/components/UserList/UserRow.vue index f840b8cdf77..ed42db76e07 100644 --- a/apps/settings/src/components/UserList/UserRow.vue +++ b/apps/settings/src/components/UserList/UserRow.vue @@ -76,6 +76,7 @@ <form :class="{'icon-loading-small': loading.displayName}" class="displayName" @submit.prevent="updateDisplayName"> + <label class="hidden-visually" :for="'displayName'+user.id+rand">{{ t('settings', 'Edit display name') }}</label> <input :id="'displayName'+user.id+rand" ref="displayName" :disabled="loading.displayName||loading.all" @@ -102,6 +103,7 @@ :class="{'icon-loading-small': loading.password}" class="password" @submit.prevent="updatePassword"> + <label class="hidden-visually" :for="'password'+user.id+rand">{{ t('settings', 'Add new password') }}</label> <input :id="'password'+user.id+rand" ref="password" :disabled="loading.password || loading.all" @@ -121,6 +123,7 @@ <form :class="{'icon-loading-small': loading.mailAddress}" class="mailAddress" @submit.prevent="updateEmail"> + <label class="hidden-visually" :for="'mailAddress'+user.id+rand">{{ t('settings', 'Add new email address') }}</label> <input :id="'mailAddress'+user.id+rand" ref="mailAddress" :disabled="loading.mailAddress||loading.all" @@ -134,7 +137,9 @@ <input class="icon-confirm" type="submit" value=""> </form> <div :class="{'icon-loading-small': loading.groups}" class="groups"> - <NcMultiselect :close-on-select="false" + <label class="hidden-visually" :for="'groups'+user.id+rand">{{ t('settings', 'Add user to group') }}</label> + <NcMultiselect :id="'groups'+user.id+rand" + :close-on-select="false" :disabled="loading.groups||loading.all" :limit="2" :multiple="true" @@ -156,7 +161,9 @@ <div v-if="subAdminsGroups.length>0 && settings.isAdmin" :class="{'icon-loading-small': loading.subadmins}" class="subadmins"> - <NcMultiselect :close-on-select="false" + <label class="hidden-visually" :for="'subadmins'+user.id+rand">{{ t('settings', 'Set user as admin for') }}</label> + <NcMultiselect :id="'subadmins'+user.id+rand" + :close-on-select="false" :disabled="loading.subadmins||loading.all" :limit="2" :multiple="true" @@ -175,7 +182,9 @@ <div :title="usedSpace" :class="{'icon-loading-small': loading.quota}" class="quota"> - <NcMultiselect :allow-empty="false" + <label class="hidden-visually" :for="'quota'+user.id+rand">{{ t('settings', 'Select user quota') }}</label> + <NcMultiselect :id="'quota'+user.id+rand" + :allow-empty="false" :disabled="loading.quota||loading.all" :options="quotaOptions" :placeholder="t('settings', 'Select user quota')" @@ -191,7 +200,9 @@ <div v-if="showConfig.showLanguages" :class="{'icon-loading-small': loading.languages}" class="languages"> - <NcMultiselect :allow-empty="false" + <label class="hidden-visually" :for="'language'+user.id+rand">{{ t('settings', 'Set the language') }}</label> + <NcMultiselect :id="'language'+user.id+rand" + :allow-empty="false" :disabled="loading.languages||loading.all" :options="languages" :placeholder="t('settings', 'No language set')" diff --git a/apps/settings/templates/settings/admin/sharing.php b/apps/settings/templates/settings/admin/sharing.php index d2c542248c1..781fdf3a49d 100644 --- a/apps/settings/templates/settings/admin/sharing.php +++ b/apps/settings/templates/settings/admin/sharing.php @@ -27,7 +27,7 @@ ?> -<div class="section" id="shareAPI"> +<div class="section loading" id="shareAPI"> <h2><?php p($l->t('Sharing'));?></h2> <?php if ($_['sharingAppEnabled'] === false) { ?> <p class="warning"><?php p($l->t('You need to enable the File sharing App.')); ?></p> diff --git a/apps/workflowengine/src/components/Check.vue b/apps/workflowengine/src/components/Check.vue index 427835a7ec7..83f823838f7 100644 --- a/apps/workflowengine/src/components/Check.vue +++ b/apps/workflowengine/src/components/Check.vue @@ -1,20 +1,20 @@ <template> <div v-click-outside="hideDelete" class="check" @click="showDelete"> - <NcMultiselect ref="checkSelector" + <NcSelect ref="checkSelector" v-model="currentOption" :options="options" label="name" track-by="class" - :allow-empty="false" + :clearable="false" :placeholder="t('workflowengine', 'Select a filter')" @input="updateCheck" /> - <NcMultiselect v-model="currentOperator" + <NcSelect v-model="currentOperator" :disabled="!currentOption" :options="operators" class="comparator" label="name" track-by="operator" - :allow-empty="false" + :clearable="false" :placeholder="t('workflowengine', 'Select a comparator')" @input="updateCheck" /> <component :is="currentOption.component" @@ -35,15 +35,22 @@ class="option" @input="updateCheck"> <NcActions v-if="deleteVisible || !currentOption"> - <NcActionButton icon="icon-close" @click="$emit('remove')" /> + <NcActionButton :title="t('workflowengine', 'Remove filter')" @click="$emit('remove')"> + <template #icon> + <CloseIcon :size="20" /> + </template> + </NcActionButton> </NcActions> </div> </template> <script> -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' + +import CloseIcon from 'vue-material-design-icons/Close.vue' + import ClickOutside from 'vue-click-outside' export default { @@ -51,7 +58,10 @@ export default { components: { NcActionButton, NcActions, - NcMultiselect, + NcSelect, + + // Icons + CloseIcon, }, directives: { ClickOutside, @@ -151,45 +161,36 @@ export default { .check { display: flex; flex-wrap: wrap; + align-items: flex-start; // to not stretch components vertically width: 100%; padding-right: 20px; + & > *:not(.close) { width: 180px; } & > .comparator { - min-width: 130px; - width: 130px; + min-width: 200px; + width: 200px; } & > .option { - min-width: 230px; - width: 230px; + min-width: 260px; + width: 260px; + min-height: 48px; + + & > input[type=text] { + min-height: 48px; + } } - & > .multiselect, + & > .v-select, + & > .button-vue, & > input[type=text] { margin-right: 5px; margin-bottom: 5px; } - - .multiselect::v-deep .multiselect__content-wrapper li>span, - .multiselect::v-deep .multiselect__single { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } } input[type=text] { margin: 0; } - ::placeholder { - font-size: 10px; - } - button.action-item.action-item--single.icon-close { - height: 44px; - width: 44px; - margin-top: -5px; - margin-bottom: -5px; - } .invalid { border-color: var(--color-error) !important; } diff --git a/apps/workflowengine/src/components/Checks/FileMimeType.vue b/apps/workflowengine/src/components/Checks/FileMimeType.vue index 472edda613a..941347f0aa2 100644 --- a/apps/workflowengine/src/components/Checks/FileMimeType.vue +++ b/apps/workflowengine/src/components/Checks/FileMimeType.vue @@ -21,48 +21,50 @@ --> <template> <div> - <NcMultiselect :value="currentValue" + <NcSelect :value="currentValue" :placeholder="t('workflowengine', 'Select a file type')" label="label" - track-by="pattern" :options="options" - :multiple="false" - :tagging="false" + :clearable="false" @input="setValue"> - <template slot="singleLabel" slot-scope="props"> - <span v-if="props.option.icon" class="option__icon" :class="props.option.icon" /> - <img v-else - class="option__icon-img" - :src="props.option.iconUrl" - alt=""> - <span class="option__title option__title_single">{{ props.option.label }}</span> + <template #option="option"> + <span v-if="option.icon" class="option__icon" :class="option.icon" /> + <span v-else class="option__icon-img"> + <img :src="option.iconUrl" alt=""> + </span> + <span class="option__title"> + <NcEllipsisedOption :name="String(option.label)" /> + </span> </template> - <template slot="option" slot-scope="props"> - <span v-if="props.option.icon" class="option__icon" :class="props.option.icon" /> - <img v-else - class="option__icon-img" - :src="props.option.iconUrl" - alt=""> - <span class="option__title">{{ props.option.label }}</span> + <template #selected-option="selectedOption"> + <span v-if="selectedOption.icon" class="option__icon" :class="selectedOption.icon" /> + <span v-else class="option__icon-img"> + <img :src="selectedOption.iconUrl" alt=""> + </span> + <span class="option__title"> + <NcEllipsisedOption :name="String(selectedOption.label)" /> + </span> </template> - </NcMultiselect> + </NcSelect> <input v-if="!isPredefined" type="text" - :value="currentValue.pattern" + :value="currentValue.id" :placeholder="t('workflowengine', 'e.g. httpd/unix-directory')" @input="updateCustom"> </div> </template> <script> -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import NcEllipsisedOption from '@nextcloud/vue/dist/Components/NcEllipsisedOption.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import valueMixin from './../../mixins/valueMixin.js' import { imagePath } from '@nextcloud/router' export default { name: 'FileMimeType', components: { - NcMultiselect, + NcEllipsisedOption, + NcSelect, }, mixins: [ valueMixin, @@ -73,22 +75,22 @@ export default { { icon: 'icon-folder', label: t('workflowengine', 'Folder'), - pattern: 'httpd/unix-directory', + id: 'httpd/unix-directory', }, { icon: 'icon-picture', label: t('workflowengine', 'Images'), - pattern: '/image\\/.*/', + id: '/image\\/.*/', }, { iconUrl: imagePath('core', 'filetypes/x-office-document'), label: t('workflowengine', 'Office documents'), - pattern: '/(vnd\\.(ms-|openxmlformats-|oasis\\.opendocument).*)$/', + id: '/(vnd\\.(ms-|openxmlformats-|oasis\\.opendocument).*)$/', }, { iconUrl: imagePath('core', 'filetypes/application-pdf'), label: t('workflowengine', 'PDF documents'), - pattern: 'application/pdf', + id: 'application/pdf', }, ], } @@ -98,7 +100,7 @@ export default { return [...this.predefinedTypes, this.customValue] }, isPredefined() { - const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern) + const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.id) if (matchingPredefined) { return true } @@ -108,18 +110,18 @@ export default { return { icon: 'icon-settings-dark', label: t('workflowengine', 'Custom MIME type'), - pattern: '', + id: '', } }, currentValue() { - const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.pattern) + const matchingPredefined = this.predefinedTypes.find((type) => this.newValue === type.id) if (matchingPredefined) { return matchingPredefined } return { icon: 'icon-settings-dark', label: t('workflowengine', 'Custom mimetype'), - pattern: this.newValue, + id: this.newValue, } }, }, @@ -131,7 +133,7 @@ export default { }, setValue(value) { if (value !== null) { - this.newValue = value.pattern + this.newValue = value.id this.$emit('input', this.newValue) } }, @@ -143,24 +145,30 @@ export default { } </script> <style scoped lang="scss"> -.multiselect, input[type='text'] { +.v-select, +input[type='text'] { width: 100%; } -.multiselect::v-deep .multiselect__content-wrapper li > span, -.multiselect::v-deep .multiselect__single { - display: flex; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + +input[type=text] { + min-height: 48px; } -.option__icon { +.option__icon, +.option__icon-img { display: inline-block; min-width: 30px; - background-position: left; + background-position: center; + vertical-align: middle; } .option__icon-img { - margin-right: 14px; + text-align: center; +} + +.option__title { + display: inline-flex; + width: calc(100% - 36px); + vertical-align: middle; } </style> diff --git a/apps/workflowengine/src/components/Checks/RequestTime.vue b/apps/workflowengine/src/components/Checks/RequestTime.vue index 79a91c0e544..36177e6ad65 100644 --- a/apps/workflowengine/src/components/Checks/RequestTime.vue +++ b/apps/workflowengine/src/components/Checks/RequestTime.vue @@ -12,15 +12,16 @@ <p v-if="!valid" class="invalid-hint"> {{ t('workflowengine', 'Please enter a valid time span') }} </p> - <NcMultiselect v-show="valid" + <NcSelect v-show="valid" v-model="newValue.timezone" + :clearable="false" :options="timezones" @input="update" /> </div> </template> <script> -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import moment from 'moment-timezone' import valueMixin from '../../mixins/valueMixin.js' @@ -28,7 +29,7 @@ const zones = moment.tz.names() export default { name: 'RequestTime', components: { - NcMultiselect, + NcSelect, }, mixins: [ valueMixin, @@ -112,6 +113,7 @@ export default { width: 50%; margin: 0; margin-bottom: 5px; + min-height: 48px; &.timeslot--start { margin-right: 5px; diff --git a/apps/workflowengine/src/components/Checks/RequestURL.vue b/apps/workflowengine/src/components/Checks/RequestURL.vue index 28184a52eb5..ce5e009cde9 100644 --- a/apps/workflowengine/src/components/Checks/RequestURL.vue +++ b/apps/workflowengine/src/components/Checks/RequestURL.vue @@ -22,41 +22,43 @@ <template> <div> - <NcMultiselect :value="currentValue" + <NcSelect :value="currentValue" :placeholder="t('workflowengine', 'Select a request URL')" label="label" - track-by="pattern" - group-values="children" - group-label="label" + :clearable="false" :options="options" - :multiple="false" - :tagging="false" @input="setValue"> - <template slot="singleLabel" slot-scope="props"> - <span class="option__icon" :class="props.option.icon" /> - <span class="option__title option__title_single">{{ props.option.label }}</span> + <template #option="option"> + <span class="option__icon" :class="option.icon" /> + <span class="option__title"> + <NcEllipsisedOption :name="String(option.label)" /> + </span> </template> - <template slot="option" slot-scope="props"> - <span class="option__icon" :class="props.option.icon" /> - <span class="option__title">{{ props.option.label }} {{ props.option.$groupLabel }}</span> + <template #selected-option="selectedOption"> + <span class="option__icon" :class="selectedOption.icon" /> + <span class="option__title"> + <NcEllipsisedOption :name="String(selectedOption.label)" /> + </span> </template> - </NcMultiselect> + </NcSelect> <input v-if="!isPredefined" type="text" - :value="currentValue.pattern" + :value="currentValue.id" :placeholder="placeholder" @input="updateCustom"> </div> </template> <script> -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import NcEllipsisedOption from '@nextcloud/vue/dist/Components/NcEllipsisedOption.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import valueMixin from '../../mixins/valueMixin.js' export default { name: 'RequestURL', components: { - NcMultiselect, + NcEllipsisedOption, + NcSelect, }, mixins: [ valueMixin, @@ -66,10 +68,9 @@ export default { newValue: '', predefinedTypes: [ { - label: t('workflowengine', 'Predefined URLs'), - children: [ - { pattern: 'webdav', label: t('workflowengine', 'Files WebDAV') }, - ], + icon: 'icon-files-dark', + id: 'webdav', + label: t('workflowengine', 'Files WebDAV'), }, ], } @@ -86,23 +87,16 @@ export default { }, matchingPredefined() { return this.predefinedTypes - .map(groups => groups.children) - .flat() - .find((type) => this.newValue === type.pattern) + .find((type) => this.newValue === type.id) }, isPredefined() { return !!this.matchingPredefined }, customValue() { return { - label: t('workflowengine', 'Others'), - children: [ - { - icon: 'icon-settings-dark', - label: t('workflowengine', 'Custom URL'), - pattern: '', - }, - ], + icon: 'icon-settings-dark', + label: t('workflowengine', 'Custom URL'), + id: '', } }, currentValue() { @@ -112,7 +106,7 @@ export default { return { icon: 'icon-settings-dark', label: t('workflowengine', 'Custom URL'), - pattern: this.newValue, + id: this.newValue, } }, }, @@ -125,7 +119,7 @@ export default { setValue(value) { // TODO: check if value requires a regex and set the check operator according to that if (value !== null) { - this.newValue = value.pattern + this.newValue = value.id this.$emit('input', this.newValue) } }, @@ -137,13 +131,24 @@ export default { } </script> <style scoped lang="scss"> - .multiselect, input[type='text'] { + .v-select, + input[type='text'] { width: 100%; } + input[type='text'] { + min-height: 48px; + } .option__icon { display: inline-block; min-width: 30px; - background-position: left; + background-position: center; + vertical-align: middle; + } + + .option__title { + display: inline-flex; + width: calc(100% - 36px); + vertical-align: middle; } </style> diff --git a/apps/workflowengine/src/components/Checks/RequestUserAgent.vue b/apps/workflowengine/src/components/Checks/RequestUserAgent.vue index 1d00bdc238d..eccf76ae58c 100644 --- a/apps/workflowengine/src/components/Checks/RequestUserAgent.vue +++ b/apps/workflowengine/src/components/Checks/RequestUserAgent.vue @@ -22,28 +22,25 @@ <template> <div> - <NcMultiselect :value="currentValue" + <NcSelect :value="currentValue" :placeholder="t('workflowengine', 'Select a user agent')" label="label" - track-by="pattern" :options="options" - :multiple="false" - :tagging="false" + :clearable="false" @input="setValue"> - <template slot="singleLabel" slot-scope="props"> - <span class="option__icon" :class="props.option.icon" /> - <!-- v-html can be used here as t() always passes our translated strings though DOMPurify.sanitize --> - <!-- eslint-disable-next-line vue/no-v-html --> - <span class="option__title option__title_single" v-html="props.option.label" /> + <template #option="option"> + <span class="option__icon" :class="option.icon" /> + <span class="option__title"> + <NcEllipsisedOption :name="String(option.label)" /> + </span> </template> - <template slot="option" slot-scope="props"> - <span class="option__icon" :class="props.option.icon" /> - <!-- eslint-disable-next-line vue/no-v-html --> - <span v-if="props.option.$groupLabel" class="option__title" v-html="props.option.$groupLabel" /> - <!-- eslint-disable-next-line vue/no-v-html --> - <span v-else class="option__title" v-html="props.option.label" /> + <template #selected-option="selectedOption"> + <span class="option__icon" :class="selectedOption.icon" /> + <span class="option__title"> + <NcEllipsisedOption :name="String(selectedOption.label)" /> + </span> </template> - </NcMultiselect> + </NcSelect> <input v-if="!isPredefined" type="text" :value="currentValue.pattern" @@ -52,13 +49,15 @@ </template> <script> -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import NcEllipsisedOption from '@nextcloud/vue/dist/Components/NcEllipsisedOption.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import valueMixin from '../../mixins/valueMixin.js' export default { name: 'RequestUserAgent', components: { - NcMultiselect, + NcEllipsisedOption, + NcSelect, }, mixins: [ valueMixin, @@ -67,10 +66,10 @@ export default { return { newValue: '', predefinedTypes: [ - { pattern: 'android', label: t('workflowengine', 'Android client'), icon: 'icon-phone' }, - { pattern: 'ios', label: t('workflowengine', 'iOS client'), icon: 'icon-phone' }, - { pattern: 'desktop', label: t('workflowengine', 'Desktop client'), icon: 'icon-desktop' }, - { pattern: 'mail', label: t('workflowengine', 'Thunderbird & Outlook addons'), icon: 'icon-mail' }, + { id: 'android', label: t('workflowengine', 'Android client'), icon: 'icon-phone' }, + { id: 'ios', label: t('workflowengine', 'iOS client'), icon: 'icon-phone' }, + { id: 'desktop', label: t('workflowengine', 'Desktop client'), icon: 'icon-desktop' }, + { id: 'mail', label: t('workflowengine', 'Thunderbird & Outlook addons'), icon: 'icon-mail' }, ], } }, @@ -80,7 +79,7 @@ export default { }, matchingPredefined() { return this.predefinedTypes - .find((type) => this.newValue === type.pattern) + .find((type) => this.newValue === type.id) }, isPredefined() { return !!this.matchingPredefined @@ -89,7 +88,7 @@ export default { return { icon: 'icon-settings-dark', label: t('workflowengine', 'Custom user agent'), - pattern: '', + id: '', } }, currentValue() { @@ -99,7 +98,7 @@ export default { return { icon: 'icon-settings-dark', label: t('workflowengine', 'Custom user agent'), - pattern: this.newValue, + id: this.newValue, } }, }, @@ -112,7 +111,7 @@ export default { setValue(value) { // TODO: check if value requires a regex and set the check operator according to that if (value !== null) { - this.newValue = value.pattern + this.newValue = value.id this.$emit('input', this.newValue) } }, @@ -124,31 +123,24 @@ export default { } </script> <style scoped> - .multiselect, input[type='text'] { + .v-select, + input[type='text'] { width: 100%; } - - .multiselect .multiselect__content-wrapper li>span { - display: flex; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .multiselect::v-deep .multiselect__single { - width: 100%; - display: flex; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + input[type='text'] { + min-height: 48px; } + .option__icon { display: inline-block; min-width: 30px; - background-position: left; + background-position: center; + vertical-align: middle; } + .option__title { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + display: inline-flex; + width: calc(100% - 36px); + vertical-align: middle; } </style> diff --git a/apps/workflowengine/src/components/Checks/RequestUserGroup.vue b/apps/workflowengine/src/components/Checks/RequestUserGroup.vue index cfb9c7dcc98..542fa46765e 100644 --- a/apps/workflowengine/src/components/Checks/RequestUserGroup.vue +++ b/apps/workflowengine/src/components/Checks/RequestUserGroup.vue @@ -22,10 +22,10 @@ <template> <div> - <NcMultiselect :value="currentValue" + <NcSelect :value="currentValue" :loading="status.isLoading && groups.length === 0" :options="groups" - :multiple="false" + :clearable="false" label="displayname" track-by="id" @search-change="searchAsync" @@ -34,7 +34,7 @@ </template> <script> -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' @@ -46,7 +46,7 @@ const status = { export default { name: 'RequestUserGroup', components: { - NcMultiselect, + NcSelect, }, props: { value: { @@ -106,7 +106,7 @@ export default { } </script> <style scoped> - .multiselect { - width: 100%; - } +.v-select { + width: 100%; +} </style> diff --git a/apps/workflowengine/src/components/Rule.vue b/apps/workflowengine/src/components/Rule.vue index 6b0abed88c8..e641d8cffb8 100644 --- a/apps/workflowengine/src/components/Rule.vue +++ b/apps/workflowengine/src/components/Rule.vue @@ -18,7 +18,7 @@ <input v-if="lastCheckComplete" type="button" class="check--add" - value="Add a new filter" + :value="t('workflowengine', 'Add a new filter')" @click="onAddFilter"> </p> </div> @@ -213,10 +213,11 @@ export default { flex-wrap: wrap; border-left: 5px solid var(--color-primary-element); - .trigger, .action { + .trigger, + .action { flex-grow: 1; min-height: 100px; - max-width: 700px; + max-width: 920px; } .action { max-width: 400px; @@ -247,6 +248,9 @@ export default { .trigger p:first-child span { padding-top: 3px; } + .trigger p:last-child { + padding-top: 8px; + } .check--add { background-position: 7px center; |