aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/cloud_federation_api/composer/composer/autoload_classmap.php1
-rw-r--r--apps/cloud_federation_api/composer/composer/autoload_static.php1
-rw-r--r--apps/cloud_federation_api/lib/Capabilities.php16
-rw-r--r--apps/cloud_federation_api/lib/Controller/RequestHandlerController.php106
-rw-r--r--apps/cloud_federation_api/lib/ResponseDefinitions.php45
-rw-r--r--apps/cloud_federation_api/openapi.json6
-rw-r--r--apps/comments/l10n/gl.js8
-rw-r--r--apps/comments/l10n/gl.json8
-rw-r--r--apps/dav/l10n/gl.js50
-rw-r--r--apps/dav/l10n/gl.json50
-rw-r--r--apps/dav/lib/CalDAV/CalDavBackend.php4
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php4
-rw-r--r--apps/encryption/l10n/gl.js4
-rw-r--r--apps/encryption/l10n/gl.json4
-rw-r--r--apps/federatedfilesharing/l10n/gl.js12
-rw-r--r--apps/federatedfilesharing/l10n/gl.json12
-rw-r--r--apps/files/appinfo/info.xml2
-rw-r--r--apps/files/composer/composer/autoload_classmap.php3
-rw-r--r--apps/files/composer/composer/autoload_static.php3
-rw-r--r--apps/files/l10n/gl.js26
-rw-r--r--apps/files/l10n/gl.json26
-rw-r--r--apps/files/lib/Capabilities.php4
-rw-r--r--apps/files/lib/Command/Copy.php133
-rw-r--r--apps/files/lib/Command/Move.php122
-rw-r--r--apps/files/lib/Controller/ApiController.php18
-rw-r--r--apps/files/lib/Controller/DirectEditingController.php35
-rw-r--r--apps/files/lib/Controller/DirectEditingViewController.php2
-rw-r--r--apps/files/lib/Controller/OpenLocalEditorController.php18
-rw-r--r--apps/files/lib/Controller/TemplateController.php37
-rw-r--r--apps/files/lib/Controller/TransferOwnershipController.php31
-rw-r--r--apps/files/lib/Controller/ViewController.php5
-rw-r--r--apps/files/lib/DirectEditingCapabilities.php3
-rw-r--r--apps/files/lib/ResponseDefinitions.php67
-rw-r--r--apps/files/openapi.json1904
-rw-r--r--apps/files_sharing/l10n/ar.js10
-rw-r--r--apps/files_sharing/l10n/ar.json10
-rw-r--r--apps/files_sharing/l10n/ca.js10
-rw-r--r--apps/files_sharing/l10n/ca.json10
-rw-r--r--apps/files_sharing/l10n/cs.js10
-rw-r--r--apps/files_sharing/l10n/cs.json10
-rw-r--r--apps/files_sharing/l10n/de_DE.js10
-rw-r--r--apps/files_sharing/l10n/de_DE.json10
-rw-r--r--apps/files_sharing/l10n/en_GB.js10
-rw-r--r--apps/files_sharing/l10n/en_GB.json10
-rw-r--r--apps/files_sharing/l10n/gl.js52
-rw-r--r--apps/files_sharing/l10n/gl.json52
-rw-r--r--apps/files_sharing/l10n/sr.js10
-rw-r--r--apps/files_sharing/l10n/sr.json10
-rw-r--r--apps/files_sharing/l10n/sv.js10
-rw-r--r--apps/files_sharing/l10n/sv.json10
-rw-r--r--apps/files_sharing/l10n/zh_HK.js9
-rw-r--r--apps/files_sharing/l10n/zh_HK.json9
-rw-r--r--apps/files_sharing/l10n/zh_TW.js10
-rw-r--r--apps/files_sharing/l10n/zh_TW.json10
-rw-r--r--apps/files_trashbin/l10n/zh_CN.js2
-rw-r--r--apps/files_trashbin/l10n/zh_CN.json2
-rw-r--r--apps/files_trashbin/lib/Capabilities.php2
-rw-r--r--apps/files_trashbin/lib/Controller/PreviewController.php13
-rw-r--r--apps/files_trashbin/openapi.json134
-rw-r--r--apps/files_versions/l10n/zh_CN.js6
-rw-r--r--apps/files_versions/l10n/zh_CN.json6
-rw-r--r--apps/files_versions/lib/Capabilities.php2
-rw-r--r--apps/files_versions/lib/Controller/PreviewController.php16
-rw-r--r--apps/files_versions/openapi.json141
-rw-r--r--apps/settings/css/settings.css2
-rw-r--r--apps/settings/css/settings.css.map2
-rw-r--r--apps/settings/css/settings.scss278
-rw-r--r--apps/settings/l10n/gl.js28
-rw-r--r--apps/settings/l10n/gl.json28
-rw-r--r--apps/settings/src/components/UserList.vue382
-rw-r--r--apps/settings/src/components/Users/NewUserModal.vue32
-rw-r--r--apps/settings/src/components/Users/UserListFooter.vue126
-rw-r--r--apps/settings/src/components/Users/UserListHeader.vue150
-rw-r--r--apps/settings/src/components/Users/UserRow.vue789
-rw-r--r--apps/settings/src/components/Users/UserRowActions.vue48
-rw-r--r--apps/settings/src/components/Users/UserRowSimple.vue185
-rw-r--r--apps/settings/src/components/Users/shared/styles.scss110
-rw-r--r--apps/settings/src/mixins/UserRowMixin.js38
-rw-r--r--apps/settings/src/store/users.js23
-rw-r--r--apps/settings/src/utils/userUtils.ts40
-rw-r--r--apps/settings/src/views/Users.vue60
-rw-r--r--apps/sharebymail/l10n/gl.js24
-rw-r--r--apps/sharebymail/l10n/gl.json24
-rw-r--r--apps/systemtags/l10n/gl.js32
-rw-r--r--apps/systemtags/l10n/gl.json32
-rw-r--r--apps/updatenotification/src/components/UpdateNotification.vue252
-rw-r--r--apps/updatenotification/src/init.js5
-rw-r--r--apps/user_ldap/l10n/gl.js6
-rw-r--r--apps/user_ldap/l10n/gl.json6
-rw-r--r--apps/workflowengine/l10n/de.js4
-rw-r--r--apps/workflowengine/l10n/de.json4
-rw-r--r--apps/workflowengine/lib/Check/FileMimeType.php8
-rw-r--r--apps/workflowengine/src/components/Event.vue78
-rw-r--r--apps/workflowengine/tests/Check/FileMimeTypeTest.php6
94 files changed, 4652 insertions, 1526 deletions
diff --git a/apps/cloud_federation_api/composer/composer/autoload_classmap.php b/apps/cloud_federation_api/composer/composer/autoload_classmap.php
index 94d538619a3..dd096ebf563 100644
--- a/apps/cloud_federation_api/composer/composer/autoload_classmap.php
+++ b/apps/cloud_federation_api/composer/composer/autoload_classmap.php
@@ -11,4 +11,5 @@ return array(
'OCA\\CloudFederationAPI\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
'OCA\\CloudFederationAPI\\Config' => $baseDir . '/../lib/Config.php',
'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => $baseDir . '/../lib/Controller/RequestHandlerController.php',
+ 'OCA\\CloudFederationAPI\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
);
diff --git a/apps/cloud_federation_api/composer/composer/autoload_static.php b/apps/cloud_federation_api/composer/composer/autoload_static.php
index a1dfe33706c..75557a20126 100644
--- a/apps/cloud_federation_api/composer/composer/autoload_static.php
+++ b/apps/cloud_federation_api/composer/composer/autoload_static.php
@@ -26,6 +26,7 @@ class ComposerStaticInitCloudFederationAPI
'OCA\\CloudFederationAPI\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
'OCA\\CloudFederationAPI\\Config' => __DIR__ . '/..' . '/../lib/Config.php',
'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => __DIR__ . '/..' . '/../lib/Controller/RequestHandlerController.php',
+ 'OCA\\CloudFederationAPI\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
);
public static function getInitializer(ClassLoader $loader)
diff --git a/apps/cloud_federation_api/lib/Capabilities.php b/apps/cloud_federation_api/lib/Capabilities.php
index 91fd5219215..f1398661ebe 100644
--- a/apps/cloud_federation_api/lib/Capabilities.php
+++ b/apps/cloud_federation_api/lib/Capabilities.php
@@ -3,6 +3,7 @@
* @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
+ * @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
@@ -36,6 +37,21 @@ class Capabilities implements ICapability {
/**
* Function an app uses to return the capabilities
+ *
+ * @return array{
+ * ocm: array{
+ * enabled: bool,
+ * apiVersion: string,
+ * endPoint: string,
+ * resourceTypes: array{
+ * name: string,
+ * shareTypes: string[],
+ * protocols: array{
+ * webdav: string,
+ * },
+ * }[],
+ * },
+ * }
*/
public function getCapabilities() {
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
index ef77f2fa317..416dca67160 100644
--- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
+++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
@@ -5,6 +5,7 @@
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
@@ -25,6 +26,7 @@
namespace OCA\CloudFederationAPI\Controller;
use OCA\CloudFederationAPI\Config;
+use OCA\CloudFederationAPI\ResponseDefinitions;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
@@ -44,11 +46,13 @@ use OCP\Share\Exceptions\ShareNotFound;
use Psr\Log\LoggerInterface;
/**
- * Class RequestHandlerController
- *
- * handle API between different Cloud instances
+ * Open-Cloud-Mesh-API
*
* @package OCA\CloudFederationAPI\Controller
+ *
+ * @psalm-import-type CloudFederationApiAddShare from ResponseDefinitions
+ * @psalm-import-type CloudFederationApiValidationError from ResponseDefinitions
+ * @psalm-import-type CloudFederationApiError from ResponseDefinitions
*/
class RequestHandlerController extends Controller {
@@ -100,26 +104,28 @@ class RequestHandlerController extends Controller {
}
/**
- * add share
+ * Add share
*
* @NoCSRFRequired
* @PublicPage
* @BruteForceProtection(action=receiveFederatedShare)
*
- * @param string $shareWith
- * @param string $name resource name (e.g. document.odt)
- * @param string $description share description (optional)
- * @param string $providerId resource UID on the provider side
- * @param string $owner provider specific UID of the user who owns the resource
- * @param string $ownerDisplayName display name of the user who shared the item
- * @param string $sharedBy provider specific UID of the user who shared the resource
- * @param string $sharedByDisplayName display name of the user who shared the resource
- * @param array $protocol (e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]])
- * @param string $shareType ('group' or 'user' share)
- * @param $resourceType ('file', 'calendar',...)
- * @return Http\DataResponse|JSONResponse
+ * @param string $shareWith The user who the share will be shared with
+ * @param string $name The resource name (e.g. document.odt)
+ * @param string|null $description Share description
+ * @param string $providerId Resource UID on the provider side
+ * @param string $owner Provider specific UID of the user who owns the resource
+ * @param string|null $ownerDisplayName Display name of the user who shared the item
+ * @param string|null $sharedBy Provider specific UID of the user who shared the resource
+ * @param string|null $sharedByDisplayName Display name of the user who shared the resource
+ * @param array{name: string[], options: array<string, mixed>} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
+ * @param string $shareType 'group' or 'user' share
+ * @param string $resourceType 'file', 'calendar',...
*
- * Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares
+ * @return JSONResponse<Http::STATUS_CREATED, CloudFederationApiAddShare, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationApiValidationError, array{}>|JSONResponse<Http::STATUS_NOT_IMPLEMENTED, CloudFederationApiError, array{}>
+ * 201: The notification was successfully received. The display name of the recipient might be returned in the body
+ * 400: Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing
+ * 501: Share type or the resource type is not supported
*/
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
@@ -137,7 +143,10 @@ class RequestHandlerController extends Controller {
!isset($protocol['options']['sharedSecret'])
) {
return new JSONResponse(
- ['message' => 'Missing arguments'],
+ [
+ 'message' => 'Missing arguments',
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
}
@@ -158,7 +167,10 @@ class RequestHandlerController extends Controller {
if (!$this->userManager->userExists($shareWith)) {
$response = new JSONResponse(
- ['message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
+ [
+ 'message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
$response->throttle();
@@ -169,7 +181,10 @@ class RequestHandlerController extends Controller {
if ($shareType === 'group') {
if (!$this->groupManager->groupExists($shareWith)) {
$response = new JSONResponse(
- ['message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
+ [
+ 'message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl(),
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
$response->throttle();
@@ -192,20 +207,18 @@ class RequestHandlerController extends Controller {
$share = $this->factory->getCloudFederationShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, '', $shareType, $resourceType);
$share->setProtocol($protocol);
$provider->shareReceived($share);
- } catch (ProviderDoesNotExistsException $e) {
+ } catch (ProviderDoesNotExistsException|ProviderCouldNotAddShareException $e) {
return new JSONResponse(
['message' => $e->getMessage()],
Http::STATUS_NOT_IMPLEMENTED
);
- } catch (ProviderCouldNotAddShareException $e) {
- return new JSONResponse(
- ['message' => $e->getMessage()],
- $e->getCode()
- );
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return new JSONResponse(
- ['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
+ [
+ 'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
}
@@ -222,19 +235,24 @@ class RequestHandlerController extends Controller {
}
/**
- * receive notification about existing share
+ * Send a notification about an existing share
*
* @NoCSRFRequired
* @PublicPage
* @BruteForceProtection(action=receiveFederatedShareNotification)
*
- * @param string $notificationType (notification type, e.g. SHARE_ACCEPTED)
- * @param string $resourceType (calendar, file, contact,...)
- * @param string $providerId id of the share
- * @param array $notification the actual payload of the notification
- * @return JSONResponse
+ * @param string $notificationType Notification type, e.g. SHARE_ACCEPTED
+ * @param string $resourceType calendar, file, contact,...
+ * @param string|null $providerId ID of the share
+ * @param array<string, mixed>|null $notification The actual payload of the notification
+ *
+ * @return JSONResponse<Http::STATUS_CREATED, array<string, mixed>, array{}>|JSONResponse<Http::STATUS_BAD_REQUEST, CloudFederationApiValidationError, array{}>|JSONResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_IMPLEMENTED, CloudFederationApiError, array{}>
+ * 201: The notification was successfully received
+ * 400: Bad request due to invalid parameters, e.g. when `type` is invalid or missing
+ * 403: Getting resource is not allowed
+ * 501: The resource type is not supported
*/
- public function receiveNotification($notificationType, $resourceType, $providerId, array $notification) {
+ public function receiveNotification($notificationType, $resourceType, $providerId, ?array $notification) {
// check if all required parameters are set
if ($notificationType === null ||
@@ -243,7 +261,10 @@ class RequestHandlerController extends Controller {
!is_array($notification)
) {
return new JSONResponse(
- ['message' => 'Missing arguments'],
+ [
+ 'message' => 'Missing arguments',
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
}
@@ -253,12 +274,18 @@ class RequestHandlerController extends Controller {
$result = $provider->notificationReceived($notificationType, $providerId, $notification);
} catch (ProviderDoesNotExistsException $e) {
return new JSONResponse(
- ['message' => $e->getMessage()],
+ [
+ 'message' => $e->getMessage(),
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
} catch (ShareNotFound $e) {
$response = new JSONResponse(
- ['message' => $e->getMessage()],
+ [
+ 'message' => $e->getMessage(),
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
$response->throttle();
@@ -276,7 +303,10 @@ class RequestHandlerController extends Controller {
return $response;
} catch (\Exception $e) {
return new JSONResponse(
- ['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
+ [
+ 'message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl(),
+ 'validationErrors' => [],
+ ],
Http::STATUS_BAD_REQUEST
);
}
diff --git a/apps/cloud_federation_api/lib/ResponseDefinitions.php b/apps/cloud_federation_api/lib/ResponseDefinitions.php
new file mode 100644
index 00000000000..06b8124cc32
--- /dev/null
+++ b/apps/cloud_federation_api/lib/ResponseDefinitions.php
@@ -0,0 +1,45 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
+ *
+ * @author Kate Döen <kate.doeen@nextcloud.com>
+ *
+ * @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\CloudFederationAPI;
+
+/**
+ * @psalm-type CloudFederationApiAddShare = array{
+ * recipientDisplayName: string,
+ * }
+ *
+ * @psalm-type CloudFederationApiError = array{
+ * message: string,
+ * }
+ *
+ * @psalm-type CloudFederationApiValidationError = CloudFederationApiError&array{
+ * validationErrors: array{
+ * name: string,
+ * message: string|null,
+ * }[],
+ * }
+ */
+class ResponseDefinitions {
+}
diff --git a/apps/cloud_federation_api/openapi.json b/apps/cloud_federation_api/openapi.json
index f017b864a27..ecefd063548 100644
--- a/apps/cloud_federation_api/openapi.json
+++ b/apps/cloud_federation_api/openapi.json
@@ -351,8 +351,8 @@
"content": {
"application/json": {
"schema": {
- "type": "array",
- "items": {
+ "type": "object",
+ "additionalProperties": {
"type": "object"
}
}
@@ -370,7 +370,7 @@
}
},
"403": {
- "description": "Getting resource not allowed",
+ "description": "Getting resource is not allowed",
"content": {
"application/json": {
"schema": {
diff --git a/apps/comments/l10n/gl.js b/apps/comments/l10n/gl.js
index edf34600bb2..5e17898ac0a 100644
--- a/apps/comments/l10n/gl.js
+++ b/apps/comments/l10n/gl.js
@@ -2,15 +2,15 @@ OC.L10N.register(
"comments",
{
"Comments" : "Comentarios",
- "You commented" : "Vostede comentou",
+ "You commented" : "Vde. comentou",
"{author} commented" : "{author} comentou",
- "You commented on %1$s" : "Vostede comentou en %1$s",
- "You commented on {file}" : "Vostede comentou en {file}",
+ "You commented on %1$s" : "Vde. comentou en %1$s",
+ "You commented on {file}" : "Vde. comentou en {file}",
"%1$s commented on %2$s" : "%1$s comentados en %2$s",
"{author} commented on {file}" : "{author} comentou en {file}",
"<strong>Comments</strong> for files" : "<strong>Comentarios</strong> para ficheiros",
"You were mentioned on \"{file}\", in a comment by a user that has since been deleted" : "Mencionárono en «{file}», nun comentario dun usuario que xa foi eliminado",
- "{user} mentioned you in a comment on \"{file}\"" : "{user} mencionouno a vostede nun comentario en «{file}»",
+ "{user} mentioned you in a comment on \"{file}\"" : "{user} mencionouno a Vde. nun comentario en «{file}»",
"Files app plugin to add comments to files" : "Complemento da aplicación de ficheiros para engadir comentarios aos ficheiros",
"Edit comment" : "Editar comentario",
"Delete comment" : "Eliminar comentario",
diff --git a/apps/comments/l10n/gl.json b/apps/comments/l10n/gl.json
index 676ab8ecfc6..cbbadea8659 100644
--- a/apps/comments/l10n/gl.json
+++ b/apps/comments/l10n/gl.json
@@ -1,14 +1,14 @@
{ "translations": {
"Comments" : "Comentarios",
- "You commented" : "Vostede comentou",
+ "You commented" : "Vde. comentou",
"{author} commented" : "{author} comentou",
- "You commented on %1$s" : "Vostede comentou en %1$s",
- "You commented on {file}" : "Vostede comentou en {file}",
+ "You commented on %1$s" : "Vde. comentou en %1$s",
+ "You commented on {file}" : "Vde. comentou en {file}",
"%1$s commented on %2$s" : "%1$s comentados en %2$s",
"{author} commented on {file}" : "{author} comentou en {file}",
"<strong>Comments</strong> for files" : "<strong>Comentarios</strong> para ficheiros",
"You were mentioned on \"{file}\", in a comment by a user that has since been deleted" : "Mencionárono en «{file}», nun comentario dun usuario que xa foi eliminado",
- "{user} mentioned you in a comment on \"{file}\"" : "{user} mencionouno a vostede nun comentario en «{file}»",
+ "{user} mentioned you in a comment on \"{file}\"" : "{user} mencionouno a Vde. nun comentario en «{file}»",
"Files app plugin to add comments to files" : "Complemento da aplicación de ficheiros para engadir comentarios aos ficheiros",
"Edit comment" : "Editar comentario",
"Delete comment" : "Eliminar comentario",
diff --git a/apps/dav/l10n/gl.js b/apps/dav/l10n/gl.js
index 288ad39f189..31e33bcfc86 100644
--- a/apps/dav/l10n/gl.js
+++ b/apps/dav/l10n/gl.js
@@ -9,29 +9,29 @@ OC.L10N.register(
"{actor} deleted calendar {calendar}" : "{actor} eliminou o calendario {calendar}",
"You deleted calendar {calendar}" : "Eliminou o calendario {calendar}",
"{actor} updated calendar {calendar}" : "{actor} actualizou o calendario {calendar}",
- "You updated calendar {calendar}" : "Vostede actualizou o calendario {calendar}",
+ "You updated calendar {calendar}" : "Vde. actualizou o calendario {calendar}",
"{actor} restored calendar {calendar}" : "{actor} restaurou o calendario {calendar}",
- "You restored calendar {calendar}" : "Vostede restaurou o calendario {calendar}",
- "You shared calendar {calendar} as public link" : "Vostede compartiu o calendario {calendar} como ligazón pública",
- "You removed public link for calendar {calendar}" : "Vostede retirou a ligazón pública do calendario {calendar}",
- "{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con vostede",
- "You shared calendar {calendar} with {user}" : "Vostede compartiu o calendario {calendar} con {user}",
+ "You restored calendar {calendar}" : "Vde. restaurou o calendario {calendar}",
+ "You shared calendar {calendar} as public link" : "Vde. compartiu o calendario {calendar} como ligazón pública",
+ "You removed public link for calendar {calendar}" : "Vde. retirou a ligazón pública do calendario {calendar}",
+ "{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con Vde.",
+ "You shared calendar {calendar} with {user}" : "Vde. compartiu o calendario {calendar} con {user}",
"{actor} shared calendar {calendar} with {user}" : "{actor} compartiu o calendario {calendar} con {user}",
- "{actor} unshared calendar {calendar} from you" : "{actor} deixou de compartir o calendario {calendar} de vostede",
- "You unshared calendar {calendar} from {user}" : "Vostede deixou de compartir o calendario {calendar} de {user}",
+ "{actor} unshared calendar {calendar} from you" : "{actor} deixou de compartir o calendario {calendar} de Vde.",
+ "You unshared calendar {calendar} from {user}" : "Vde. deixou de compartir o calendario {calendar} de {user}",
"{actor} unshared calendar {calendar} from {user}" : "{actor} deixou de compartir o calendario {calendar} de {user}",
"{actor} unshared calendar {calendar} from themselves" : "{actor} deixou de compartir o seu propio calendario {calendar}",
- "You shared calendar {calendar} with group {group}" : "Vostede compartiu o calendario {calendar} co grupo {group}",
+ "You shared calendar {calendar} with group {group}" : "Vde. compartiu o calendario {calendar} co grupo {group}",
"{actor} shared calendar {calendar} with group {group}" : "{actor} compartiu o calendario {calendar} co grupo {group}",
- "You unshared calendar {calendar} from group {group}" : "Vostede deixou de compartir o calendario {calendar} do grupo {group}",
+ "You unshared calendar {calendar} from group {group}" : "Vde. deixou de compartir o calendario {calendar} do grupo {group}",
"{actor} unshared calendar {calendar} from group {group}" : "{actor} deixou de compartir o calendario {calendar} do grupo {group}",
"Untitled event" : "Evento sen título",
"{actor} created event {event} in calendar {calendar}" : "{actor} creou o evento {event} no calendario {calendar}",
- "You created event {event} in calendar {calendar}" : "Vostede creou o evento {event} no calendario {calendar}",
+ "You created event {event} in calendar {calendar}" : "Vde. creou o evento {event} no calendario {calendar}",
"{actor} deleted event {event} from calendar {calendar}" : "{actor} eliminou o evento {event} do calendario {calendar}",
- "You deleted event {event} from calendar {calendar}" : "Vostede eliminou o evento {event} do calendario {calendar}",
+ "You deleted event {event} from calendar {calendar}" : "Vde. eliminou o evento {event} do calendario {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} actualizou o evento {event} no calendario {calendar}",
- "You updated event {event} in calendar {calendar}" : "Vostede actualizou o evento {event} no calendario {calendar}",
+ "You updated event {event} in calendar {calendar}" : "Vde. actualizou o evento {event} no calendario {calendar}",
"{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} moveu o evento {event} do calendario {sourceCalendar} ao calendario {targetCalendar}",
"You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Moveu o evento {evento} do calendario {sourceCalendar} ao calendario {targetCalendar}",
"{actor} restored event {event} of calendar {calendar}" : "{actor} restaurou o evento {evento} do calendario {calendar}",
@@ -95,28 +95,28 @@ OC.L10N.register(
"More options at %s" : "Máis opcións en %s",
"Contacts" : "Contactos",
"{actor} created address book {addressbook}" : "{actor} creou o caderno de enderezos {addressbook}",
- "You created address book {addressbook}" : "Vostede creou o caderno de enderezos {addressbook}",
+ "You created address book {addressbook}" : "Vde. creou o caderno de enderezos {addressbook}",
"{actor} deleted address book {addressbook}" : "{actor} eliminou o caderno de enderezos {addressbook}",
- "You deleted address book {addressbook}" : "Vostede eliminou o caderno de enderezos {addressbook}",
+ "You deleted address book {addressbook}" : "Vde. eliminou o caderno de enderezos {addressbook}",
"{actor} updated address book {addressbook}" : "{actor} actualizou o caderno de enderezos {addressbook}",
- "You updated address book {addressbook}" : "Vostede actualizou o caderno de enderezos {addressbook}",
- "{actor} shared address book {addressbook} with you" : "{actor} compartiu o caderno de enderezos {addressbook} con vostede",
- "You shared address book {addressbook} with {user}" : "Vostede compartiu o caderno de enderezos {addressbook} con {user}",
+ "You updated address book {addressbook}" : "Vde. actualizou o caderno de enderezos {addressbook}",
+ "{actor} shared address book {addressbook} with you" : "{actor} compartiu o caderno de enderezos {addressbook} con Vde.",
+ "You shared address book {addressbook} with {user}" : "Vde. compartiu o caderno de enderezos {addressbook} con {user}",
"{actor} shared address book {addressbook} with {user}" : "{actor} compartiu o caderno de enderezos {addressbook} con {user}",
"{actor} unshared address book {addressbook} from you" : "{actor} deixou de compartir o seu caderno de enderezos {addressbook}",
- "You unshared address book {addressbook} from {user}" : "Vostede deixou de compartir o caderno de enderezos {addressbook} de {user}",
+ "You unshared address book {addressbook} from {user}" : "Vde. deixou de compartir o caderno de enderezos {addressbook} de {user}",
"{actor} unshared address book {addressbook} from {user}" : "{actor} deixou de compartir o caderno de enderezos {addressbook} de {user}",
"{actor} unshared address book {addressbook} from themselves" : "{actor} deixaron de compartir o seu caderno de enderezos {addressbook}",
- "You shared address book {addressbook} with group {group}" : "Vostede compartiu o caderno de enderezos {addressbook} co grupo {group}",
+ "You shared address book {addressbook} with group {group}" : "Vde. compartiu o caderno de enderezos {addressbook} co grupo {group}",
"{actor} shared address book {addressbook} with group {group}" : "{actor} compartiu o caderno de enderezos {addressbook} co grupo {group}",
- "You unshared address book {addressbook} from group {group}" : "Vostede deixou de compartir o caderno de enderezos {addressbook} do grupo {group}",
+ "You unshared address book {addressbook} from group {group}" : "Vde. deixou de compartir o caderno de enderezos {addressbook} do grupo {group}",
"{actor} unshared address book {addressbook} from group {group}" : "{actor} deixou de compartir o caderno de enderezos {addressbook} do grupo {group}",
"{actor} created contact {card} in address book {addressbook}" : "{actor} creou o contacto {card} no caderno de enderezos {addressbook}",
- "You created contact {card} in address book {addressbook}" : "Vostede creou o contacto {card} no caderno de enderezos {addressbook}",
+ "You created contact {card} in address book {addressbook}" : "Vde. creou o contacto {card} no caderno de enderezos {addressbook}",
"{actor} deleted contact {card} from address book {addressbook}" : "{actor} eliminou o contacto {card} do caderno de enderezos {addressbook}",
- "You deleted contact {card} from address book {addressbook}" : "Vostede eliminou o contacto {card} do caderno de enderezos {addressbook}",
+ "You deleted contact {card} from address book {addressbook}" : "Vde. eliminou o contacto {card} do caderno de enderezos {addressbook}",
"{actor} updated contact {card} in address book {addressbook}" : "{actor} actualizou o contacto {card} no caderno de enderezos {addressbook}",
- "You updated contact {card} in address book {addressbook}" : "Vostede actualizou o contacto {card} no caderno de enderezos {addressbook}",
+ "You updated contact {card} in address book {addressbook}" : "Vde. actualizou o contacto {card} no caderno de enderezos {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "Foi modificado un <strong>contacto</strong> ou <strong>caderno de enderezos</strong>",
"Accounts" : "Contas",
"System address book which holds all accounts" : "Caderno de enderezos do sistema que contén todas as contas",
@@ -186,7 +186,7 @@ OC.L10N.register(
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Asegúrese de ter configurado correctamente {emailopen}o servidor de correo-e{linkclose}.",
"There was an error updating your attendance status." : "Produciuse un erro ao actualizar o seu estado de asistencia.",
"Please contact the organizer directly." : "Contacte directamente co organizador.",
- "Are you accepting the invitation?" : "Acepta vostede o convite?",
+ "Are you accepting the invitation?" : "Acepta Vde. o convite?",
"Tentative" : "Provisional",
"Your attendance was updated successfully." : "A súa asistencia foi actualizada satisfactoriamente.",
"Invitation canceled" : "Convite cancelado",
diff --git a/apps/dav/l10n/gl.json b/apps/dav/l10n/gl.json
index 17e88139f67..b39cbc9d598 100644
--- a/apps/dav/l10n/gl.json
+++ b/apps/dav/l10n/gl.json
@@ -7,29 +7,29 @@
"{actor} deleted calendar {calendar}" : "{actor} eliminou o calendario {calendar}",
"You deleted calendar {calendar}" : "Eliminou o calendario {calendar}",
"{actor} updated calendar {calendar}" : "{actor} actualizou o calendario {calendar}",
- "You updated calendar {calendar}" : "Vostede actualizou o calendario {calendar}",
+ "You updated calendar {calendar}" : "Vde. actualizou o calendario {calendar}",
"{actor} restored calendar {calendar}" : "{actor} restaurou o calendario {calendar}",
- "You restored calendar {calendar}" : "Vostede restaurou o calendario {calendar}",
- "You shared calendar {calendar} as public link" : "Vostede compartiu o calendario {calendar} como ligazón pública",
- "You removed public link for calendar {calendar}" : "Vostede retirou a ligazón pública do calendario {calendar}",
- "{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con vostede",
- "You shared calendar {calendar} with {user}" : "Vostede compartiu o calendario {calendar} con {user}",
+ "You restored calendar {calendar}" : "Vde. restaurou o calendario {calendar}",
+ "You shared calendar {calendar} as public link" : "Vde. compartiu o calendario {calendar} como ligazón pública",
+ "You removed public link for calendar {calendar}" : "Vde. retirou a ligazón pública do calendario {calendar}",
+ "{actor} shared calendar {calendar} with you" : "{actor} compartiu o calendario {calendar} con Vde.",
+ "You shared calendar {calendar} with {user}" : "Vde. compartiu o calendario {calendar} con {user}",
"{actor} shared calendar {calendar} with {user}" : "{actor} compartiu o calendario {calendar} con {user}",
- "{actor} unshared calendar {calendar} from you" : "{actor} deixou de compartir o calendario {calendar} de vostede",
- "You unshared calendar {calendar} from {user}" : "Vostede deixou de compartir o calendario {calendar} de {user}",
+ "{actor} unshared calendar {calendar} from you" : "{actor} deixou de compartir o calendario {calendar} de Vde.",
+ "You unshared calendar {calendar} from {user}" : "Vde. deixou de compartir o calendario {calendar} de {user}",
"{actor} unshared calendar {calendar} from {user}" : "{actor} deixou de compartir o calendario {calendar} de {user}",
"{actor} unshared calendar {calendar} from themselves" : "{actor} deixou de compartir o seu propio calendario {calendar}",
- "You shared calendar {calendar} with group {group}" : "Vostede compartiu o calendario {calendar} co grupo {group}",
+ "You shared calendar {calendar} with group {group}" : "Vde. compartiu o calendario {calendar} co grupo {group}",
"{actor} shared calendar {calendar} with group {group}" : "{actor} compartiu o calendario {calendar} co grupo {group}",
- "You unshared calendar {calendar} from group {group}" : "Vostede deixou de compartir o calendario {calendar} do grupo {group}",
+ "You unshared calendar {calendar} from group {group}" : "Vde. deixou de compartir o calendario {calendar} do grupo {group}",
"{actor} unshared calendar {calendar} from group {group}" : "{actor} deixou de compartir o calendario {calendar} do grupo {group}",
"Untitled event" : "Evento sen título",
"{actor} created event {event} in calendar {calendar}" : "{actor} creou o evento {event} no calendario {calendar}",
- "You created event {event} in calendar {calendar}" : "Vostede creou o evento {event} no calendario {calendar}",
+ "You created event {event} in calendar {calendar}" : "Vde. creou o evento {event} no calendario {calendar}",
"{actor} deleted event {event} from calendar {calendar}" : "{actor} eliminou o evento {event} do calendario {calendar}",
- "You deleted event {event} from calendar {calendar}" : "Vostede eliminou o evento {event} do calendario {calendar}",
+ "You deleted event {event} from calendar {calendar}" : "Vde. eliminou o evento {event} do calendario {calendar}",
"{actor} updated event {event} in calendar {calendar}" : "{actor} actualizou o evento {event} no calendario {calendar}",
- "You updated event {event} in calendar {calendar}" : "Vostede actualizou o evento {event} no calendario {calendar}",
+ "You updated event {event} in calendar {calendar}" : "Vde. actualizou o evento {event} no calendario {calendar}",
"{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "{actor} moveu o evento {event} do calendario {sourceCalendar} ao calendario {targetCalendar}",
"You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}" : "Moveu o evento {evento} do calendario {sourceCalendar} ao calendario {targetCalendar}",
"{actor} restored event {event} of calendar {calendar}" : "{actor} restaurou o evento {evento} do calendario {calendar}",
@@ -93,28 +93,28 @@
"More options at %s" : "Máis opcións en %s",
"Contacts" : "Contactos",
"{actor} created address book {addressbook}" : "{actor} creou o caderno de enderezos {addressbook}",
- "You created address book {addressbook}" : "Vostede creou o caderno de enderezos {addressbook}",
+ "You created address book {addressbook}" : "Vde. creou o caderno de enderezos {addressbook}",
"{actor} deleted address book {addressbook}" : "{actor} eliminou o caderno de enderezos {addressbook}",
- "You deleted address book {addressbook}" : "Vostede eliminou o caderno de enderezos {addressbook}",
+ "You deleted address book {addressbook}" : "Vde. eliminou o caderno de enderezos {addressbook}",
"{actor} updated address book {addressbook}" : "{actor} actualizou o caderno de enderezos {addressbook}",
- "You updated address book {addressbook}" : "Vostede actualizou o caderno de enderezos {addressbook}",
- "{actor} shared address book {addressbook} with you" : "{actor} compartiu o caderno de enderezos {addressbook} con vostede",
- "You shared address book {addressbook} with {user}" : "Vostede compartiu o caderno de enderezos {addressbook} con {user}",
+ "You updated address book {addressbook}" : "Vde. actualizou o caderno de enderezos {addressbook}",
+ "{actor} shared address book {addressbook} with you" : "{actor} compartiu o caderno de enderezos {addressbook} con Vde.",
+ "You shared address book {addressbook} with {user}" : "Vde. compartiu o caderno de enderezos {addressbook} con {user}",
"{actor} shared address book {addressbook} with {user}" : "{actor} compartiu o caderno de enderezos {addressbook} con {user}",
"{actor} unshared address book {addressbook} from you" : "{actor} deixou de compartir o seu caderno de enderezos {addressbook}",
- "You unshared address book {addressbook} from {user}" : "Vostede deixou de compartir o caderno de enderezos {addressbook} de {user}",
+ "You unshared address book {addressbook} from {user}" : "Vde. deixou de compartir o caderno de enderezos {addressbook} de {user}",
"{actor} unshared address book {addressbook} from {user}" : "{actor} deixou de compartir o caderno de enderezos {addressbook} de {user}",
"{actor} unshared address book {addressbook} from themselves" : "{actor} deixaron de compartir o seu caderno de enderezos {addressbook}",
- "You shared address book {addressbook} with group {group}" : "Vostede compartiu o caderno de enderezos {addressbook} co grupo {group}",
+ "You shared address book {addressbook} with group {group}" : "Vde. compartiu o caderno de enderezos {addressbook} co grupo {group}",
"{actor} shared address book {addressbook} with group {group}" : "{actor} compartiu o caderno de enderezos {addressbook} co grupo {group}",
- "You unshared address book {addressbook} from group {group}" : "Vostede deixou de compartir o caderno de enderezos {addressbook} do grupo {group}",
+ "You unshared address book {addressbook} from group {group}" : "Vde. deixou de compartir o caderno de enderezos {addressbook} do grupo {group}",
"{actor} unshared address book {addressbook} from group {group}" : "{actor} deixou de compartir o caderno de enderezos {addressbook} do grupo {group}",
"{actor} created contact {card} in address book {addressbook}" : "{actor} creou o contacto {card} no caderno de enderezos {addressbook}",
- "You created contact {card} in address book {addressbook}" : "Vostede creou o contacto {card} no caderno de enderezos {addressbook}",
+ "You created contact {card} in address book {addressbook}" : "Vde. creou o contacto {card} no caderno de enderezos {addressbook}",
"{actor} deleted contact {card} from address book {addressbook}" : "{actor} eliminou o contacto {card} do caderno de enderezos {addressbook}",
- "You deleted contact {card} from address book {addressbook}" : "Vostede eliminou o contacto {card} do caderno de enderezos {addressbook}",
+ "You deleted contact {card} from address book {addressbook}" : "Vde. eliminou o contacto {card} do caderno de enderezos {addressbook}",
"{actor} updated contact {card} in address book {addressbook}" : "{actor} actualizou o contacto {card} no caderno de enderezos {addressbook}",
- "You updated contact {card} in address book {addressbook}" : "Vostede actualizou o contacto {card} no caderno de enderezos {addressbook}",
+ "You updated contact {card} in address book {addressbook}" : "Vde. actualizou o contacto {card} no caderno de enderezos {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "Foi modificado un <strong>contacto</strong> ou <strong>caderno de enderezos</strong>",
"Accounts" : "Contas",
"System address book which holds all accounts" : "Caderno de enderezos do sistema que contén todas as contas",
@@ -184,7 +184,7 @@
"Please make sure to properly set up {emailopen}the email server{linkclose}." : "Asegúrese de ter configurado correctamente {emailopen}o servidor de correo-e{linkclose}.",
"There was an error updating your attendance status." : "Produciuse un erro ao actualizar o seu estado de asistencia.",
"Please contact the organizer directly." : "Contacte directamente co organizador.",
- "Are you accepting the invitation?" : "Acepta vostede o convite?",
+ "Are you accepting the invitation?" : "Acepta Vde. o convite?",
"Tentative" : "Provisional",
"Your attendance was updated successfully." : "A súa asistencia foi actualizada satisfactoriamente.",
"Invitation canceled" : "Convite cancelado",
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php
index ef41301b840..a948c54ad58 100644
--- a/apps/dav/lib/CalDAV/CalDavBackend.php
+++ b/apps/dav/lib/CalDAV/CalDavBackend.php
@@ -3139,7 +3139,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$query->select($query->func()->max('id'))
->from('calendarchanges');
- $maxId = $query->executeQuery()->fetchOne();
+ $result = $query->executeQuery();
+ $maxId = (int) $result->fetchOne();
+ $result->closeCursor();
if (!$maxId || $maxId < $keep) {
return 0;
}
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 1be1ce3f18f..045ad4d1385 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -1404,7 +1404,9 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$query->select($query->func()->max('id'))
->from('addressbookchanges');
- $maxId = $query->executeQuery()->fetchOne();
+ $result = $query->executeQuery();
+ $maxId = (int) $result->fetchOne();
+ $result->closeCursor();
if (!$maxId || $maxId < $keep) {
return 0;
}
diff --git a/apps/encryption/l10n/gl.js b/apps/encryption/l10n/gl.js
index c2a030b3a14..840f4d041f1 100644
--- a/apps/encryption/l10n/gl.js
+++ b/apps/encryption/l10n/gl.js
@@ -32,8 +32,8 @@ OC.L10N.register(
"The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>." : "A administración activou o cifrado no lado do servidor. Os seus ficheiros foron cifrados co contrasinal <strong>%s</strong>.",
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "A administración activou o cifrado no lado do servidor. Os seus ficheiros foron cifrados co contrasinal «%s».",
"Please login to the web interface, go to the \"Security\" section of your personal settings and update your encryption password by entering this password into the \"Old log-in password\" field and your current login-password." : "Acceda na interface web, vaia á sección «Seguranza» dos seus axustes persoais e actualice o seu contrasinal de cifrado introducindo este contrasinal no campo «Contrasinal antigo de acceso» e o seu contrasinal de acceso actual.",
- "Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel descifrar o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con vostede.",
- "Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel ler o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con vostede.",
+ "Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel descifrar o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con Vde.",
+ "Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel ler o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con Vde.",
"Default encryption module" : "Módulo de cifrado predeterminado",
"Default encryption module for server-side encryption" : "Módulo de cifrado predeterminado para o cifrado no lado do servidor",
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "Para usar este módulo de cifrado é preciso activar o cifrado no lado do servidor nos axustes de administración. Una vez activado este módulo cifrará todos os seus ficheiros de xeito transparente. O cifrado basease en chave AES 256.\nO módulo non tocará os ficheiros existentes, só se cifran os ficheiros novos após que se active o cifrado no lado do servidor. Tampouco é posíbel desactivar o cifrado e volver a un sistema sen cifrar.\nLea a documentación para coñecer todas as implicacións antes de decidir activar o cifrado no lado do servidor.",
diff --git a/apps/encryption/l10n/gl.json b/apps/encryption/l10n/gl.json
index 39a087dc81f..8fbaf0d1688 100644
--- a/apps/encryption/l10n/gl.json
+++ b/apps/encryption/l10n/gl.json
@@ -30,8 +30,8 @@
"The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>." : "A administración activou o cifrado no lado do servidor. Os seus ficheiros foron cifrados co contrasinal <strong>%s</strong>.",
"The administration enabled server-side-encryption. Your files were encrypted using the password \"%s\"." : "A administración activou o cifrado no lado do servidor. Os seus ficheiros foron cifrados co contrasinal «%s».",
"Please login to the web interface, go to the \"Security\" section of your personal settings and update your encryption password by entering this password into the \"Old log-in password\" field and your current login-password." : "Acceda na interface web, vaia á sección «Seguranza» dos seus axustes persoais e actualice o seu contrasinal de cifrado introducindo este contrasinal no campo «Contrasinal antigo de acceso» e o seu contrasinal de acceso actual.",
- "Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel descifrar o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con vostede.",
- "Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel ler o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con vostede.",
+ "Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel descifrar o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con Vde.",
+ "Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Non foi posíbel ler o ficheiro, probabelmente tratase dun ficheiro compartido. Pídalle ao propietario do ficheiro que volva compartir o ficheiro con Vde.",
"Default encryption module" : "Módulo de cifrado predeterminado",
"Default encryption module for server-side encryption" : "Módulo de cifrado predeterminado para o cifrado no lado do servidor",
"In order to use this encryption module you need to enable server-side encryption in the admin settings. Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.\nThe module will not touch existing files, only new files will be encrypted after server-side encryption was enabled. It is also not possible to disable the encryption again and switch back to an unencrypted system.\nPlease read the documentation to know all implications before you decide to enable server-side encryption." : "Para usar este módulo de cifrado é preciso activar o cifrado no lado do servidor nos axustes de administración. Una vez activado este módulo cifrará todos os seus ficheiros de xeito transparente. O cifrado basease en chave AES 256.\nO módulo non tocará os ficheiros existentes, só se cifran os ficheiros novos após que se active o cifrado no lado do servidor. Tampouco é posíbel desactivar o cifrado e volver a un sistema sen cifrar.\nLea a documentación para coñecer todas as implicacións antes de decidir activar o cifrado no lado do servidor.",
diff --git a/apps/federatedfilesharing/l10n/gl.js b/apps/federatedfilesharing/l10n/gl.js
index 09c61a30321..f04c1a2d238 100644
--- a/apps/federatedfilesharing/l10n/gl.js
+++ b/apps/federatedfilesharing/l10n/gl.js
@@ -10,7 +10,7 @@ OC.L10N.register(
"Server to server sharing is not enabled on this server" : "Neste servidor non está activada a compartición de servidor a servidor",
"Couldn't establish a federated share." : "Non foi posíbel estabelecer unha compartición federada",
"Couldn't establish a federated share, maybe the password was wrong." : "Non foi posíbel estabelecer unha compartición federada, é probábel que o contrasinal sexa erróneo.",
- "Federated Share request sent, you will receive an invitation. Check your notifications." : "Enviouse a solicitude dunha compartición federada, vostede recibirá unha notificación. Comprobe as súas notificacións.",
+ "Federated Share request sent, you will receive an invitation. Check your notifications." : "Enviouse a solicitude dunha compartición federada, Vde. recibirá unha notificación. Comprobe as súas notificacións.",
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Non foi posíbel estabelecer unha compartición federada, semella que o servidor federar é antigo de máis (Nextcloud <= 9).",
"It is not allowed to send federated group shares from this server." : "Non está permitido enviar unha compartición de grupos federados dende este servidor.",
"Sharing %1$s failed, because this item is already shared with user %2$s" : "Fallou a compartición de %1$s por mor de que este elemento xa foi compartido co usuario %2$s",
@@ -20,8 +20,8 @@ OC.L10N.register(
"Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Produciuse un fallo ao compartir %1$s Non foi posíbel atopar %2$s, quizais haxa un problema de conexión co servidor ou emprega un certificado autoasinado.",
"Could not find share" : "Non foi posíbel atopar o recurso compartido",
"Federated sharing" : "Compartición federada",
- "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Vostede recibiu {share} como un elemento compartido remoto de {user} (de parte de {behalf})",
- "You received {share} as a remote share from {user}" : "Vostede recibiu {share} como un elemento compartido remoto de {user}",
+ "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Vde. recibiu {share} como un elemento compartido remoto de {user} (de parte de {behalf})",
+ "You received {share} as a remote share from {user}" : "Vde. recibiu {share} como un elemento compartido remoto de {user}",
"Accept" : "Aceptar",
"Decline" : "Declinar",
"Federated Cloud Sharing" : "Nube federada compartida",
@@ -39,7 +39,7 @@ OC.L10N.register(
"Federated Cloud" : "Nube federada",
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Pode compartir con calquera persoa que empregue servidores Nextcloud, ou outro servidor ou servizo compatíbel co Open Cloud Mesh (OCM)! Simplemente, introduza o seu ID de nube federada no diálogo de compartir. É algo como persona@cloud.exemplo.com",
"Your Federated Cloud ID:" : "ID da súa nube federada:",
- "Share it so your friends can share files with you:" : "Compártao para que as súas amizades poidan compartir ficheiros con vostede:",
+ "Share it so your friends can share files with you:" : "Compártao para que as súas amizades poidan compartir ficheiros con Vde.:",
"Facebook" : "Facebook",
"Twitter" : "Twitter",
"Diaspora" : "Diaspora",
@@ -52,8 +52,8 @@ OC.L10N.register(
"Copy to clipboard" : "Copiar no portapapeis.",
"Clipboard is not available" : "O portapapeis non está dispoñíbel",
"Copied!" : "Copiado!",
- "You received \"%3$s\" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))" : "Vostede recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s) (de parte de %5$s (%2$s))",
- "You received \"%3$s\" as a remote share from %4$s (%1$s)" : "Vostede recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s)",
+ "You received \"%3$s\" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))" : "Vde. recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s) (de parte de %5$s (%2$s))",
+ "You received \"%3$s\" as a remote share from %4$s (%1$s)" : "Vde. recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s)",
"Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Comparte comigo a través do meu ID da nube federada do #Nextcloud , vexa %s"
},
"nplurals=2; plural=(n != 1);");
diff --git a/apps/federatedfilesharing/l10n/gl.json b/apps/federatedfilesharing/l10n/gl.json
index 5c8bfe1985a..30ac824f803 100644
--- a/apps/federatedfilesharing/l10n/gl.json
+++ b/apps/federatedfilesharing/l10n/gl.json
@@ -8,7 +8,7 @@
"Server to server sharing is not enabled on this server" : "Neste servidor non está activada a compartición de servidor a servidor",
"Couldn't establish a federated share." : "Non foi posíbel estabelecer unha compartición federada",
"Couldn't establish a federated share, maybe the password was wrong." : "Non foi posíbel estabelecer unha compartición federada, é probábel que o contrasinal sexa erróneo.",
- "Federated Share request sent, you will receive an invitation. Check your notifications." : "Enviouse a solicitude dunha compartición federada, vostede recibirá unha notificación. Comprobe as súas notificacións.",
+ "Federated Share request sent, you will receive an invitation. Check your notifications." : "Enviouse a solicitude dunha compartición federada, Vde. recibirá unha notificación. Comprobe as súas notificacións.",
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Non foi posíbel estabelecer unha compartición federada, semella que o servidor federar é antigo de máis (Nextcloud <= 9).",
"It is not allowed to send federated group shares from this server." : "Non está permitido enviar unha compartición de grupos federados dende este servidor.",
"Sharing %1$s failed, because this item is already shared with user %2$s" : "Fallou a compartición de %1$s por mor de que este elemento xa foi compartido co usuario %2$s",
@@ -18,8 +18,8 @@
"Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Produciuse un fallo ao compartir %1$s Non foi posíbel atopar %2$s, quizais haxa un problema de conexión co servidor ou emprega un certificado autoasinado.",
"Could not find share" : "Non foi posíbel atopar o recurso compartido",
"Federated sharing" : "Compartición federada",
- "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Vostede recibiu {share} como un elemento compartido remoto de {user} (de parte de {behalf})",
- "You received {share} as a remote share from {user}" : "Vostede recibiu {share} como un elemento compartido remoto de {user}",
+ "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Vde. recibiu {share} como un elemento compartido remoto de {user} (de parte de {behalf})",
+ "You received {share} as a remote share from {user}" : "Vde. recibiu {share} como un elemento compartido remoto de {user}",
"Accept" : "Aceptar",
"Decline" : "Declinar",
"Federated Cloud Sharing" : "Nube federada compartida",
@@ -37,7 +37,7 @@
"Federated Cloud" : "Nube federada",
"You can share with anyone who uses a Nextcloud server or other Open Cloud Mesh (OCM) compatible servers and services! Just put their Federated Cloud ID in the share dialog. It looks like person@cloud.example.com" : "Pode compartir con calquera persoa que empregue servidores Nextcloud, ou outro servidor ou servizo compatíbel co Open Cloud Mesh (OCM)! Simplemente, introduza o seu ID de nube federada no diálogo de compartir. É algo como persona@cloud.exemplo.com",
"Your Federated Cloud ID:" : "ID da súa nube federada:",
- "Share it so your friends can share files with you:" : "Compártao para que as súas amizades poidan compartir ficheiros con vostede:",
+ "Share it so your friends can share files with you:" : "Compártao para que as súas amizades poidan compartir ficheiros con Vde.:",
"Facebook" : "Facebook",
"Twitter" : "Twitter",
"Diaspora" : "Diaspora",
@@ -50,8 +50,8 @@
"Copy to clipboard" : "Copiar no portapapeis.",
"Clipboard is not available" : "O portapapeis non está dispoñíbel",
"Copied!" : "Copiado!",
- "You received \"%3$s\" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))" : "Vostede recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s) (de parte de %5$s (%2$s))",
- "You received \"%3$s\" as a remote share from %4$s (%1$s)" : "Vostede recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s)",
+ "You received \"%3$s\" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))" : "Vde. recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s) (de parte de %5$s (%2$s))",
+ "You received \"%3$s\" as a remote share from %4$s (%1$s)" : "Vde. recibiu «%3$s» como un elemento compartido remoto de %4$s (%1$s)",
"Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Comparte comigo a través do meu ID da nube federada do #Nextcloud , vexa %s"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index 202d8e3ffda..12384b9f184 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -38,6 +38,8 @@
<command>OCA\Files\Command\Get</command>
<command>OCA\Files\Command\Put</command>
<command>OCA\Files\Command\Delete</command>
+ <command>OCA\Files\Command\Copy</command>
+ <command>OCA\Files\Command\Move</command>
<command>OCA\Files\Command\Object\Delete</command>
<command>OCA\Files\Command\Object\Get</command>
<command>OCA\Files\Command\Object\Put</command>
diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php
index 88d3ae10413..c6d03ffd3be 100644
--- a/apps/files/composer/composer/autoload_classmap.php
+++ b/apps/files/composer/composer/autoload_classmap.php
@@ -27,9 +27,11 @@ 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\\Copy' => $baseDir . '/../lib/Command/Copy.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\\Move' => $baseDir . '/../lib/Command/Move.php',
'OCA\\Files\\Command\\Object\\Delete' => $baseDir . '/../lib/Command/Object/Delete.php',
'OCA\\Files\\Command\\Object\\Get' => $baseDir . '/../lib/Command/Object/Get.php',
'OCA\\Files\\Command\\Object\\ObjectUtil' => $baseDir . '/../lib/Command/Object/ObjectUtil.php',
@@ -61,6 +63,7 @@ return array(
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
+ 'OCA\\Files\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',
diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php
index c27db581e61..2cb71917bd6 100644
--- a/apps/files/composer/composer/autoload_static.php
+++ b/apps/files/composer/composer/autoload_static.php
@@ -42,9 +42,11 @@ 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\\Copy' => __DIR__ . '/..' . '/../lib/Command/Copy.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\\Move' => __DIR__ . '/..' . '/../lib/Command/Move.php',
'OCA\\Files\\Command\\Object\\Delete' => __DIR__ . '/..' . '/../lib/Command/Object/Delete.php',
'OCA\\Files\\Command\\Object\\Get' => __DIR__ . '/..' . '/../lib/Command/Object/Get.php',
'OCA\\Files\\Command\\Object\\ObjectUtil' => __DIR__ . '/..' . '/../lib/Command/Object/ObjectUtil.php',
@@ -76,6 +78,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
+ 'OCA\\Files\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php',
'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',
diff --git a/apps/files/l10n/gl.js b/apps/files/l10n/gl.js
index f65e67103f2..6ca79a01997 100644
--- a/apps/files/l10n/gl.js
+++ b/apps/files/l10n/gl.js
@@ -126,30 +126,30 @@ OC.L10N.register(
"Renamed by {user}" : "Renomeado por {user}",
"Moved by {user}" : "Movido por {user}",
"\"remote user\"" : "«usuario remoto»",
- "You created {file}" : "{file} foi creado por vostede",
- "You created an encrypted file in {file}" : "Vostede creo un ficheiro cifrado en {file}",
+ "You created {file}" : "{file} foi creado por Vde.",
+ "You created an encrypted file in {file}" : "Vde. creo un ficheiro cifrado en {file}",
"{user} created {file}" : "{user} creou {file}",
"{user} created an encrypted file in {file}" : "{user} creou un ficheiro cifrado en {file}",
"{file} was created in a public folder" : "{file} foi creado nun cartafol público",
- "You changed {file}" : "{file} foi cambiado por vostede",
- "You changed an encrypted file in {file}" : "Vostede cambiou un ficheiro cifrado en {file}",
+ "You changed {file}" : "{file} foi cambiado por Vde.",
+ "You changed an encrypted file in {file}" : "Vde. cambiou un ficheiro cifrado en {file}",
"{user} changed {file}" : "{file} foi cambiado por {user}",
"{user} changed an encrypted file in {file}" : "{user} cambiou un ficheiro cifrado en {file}",
- "You deleted {file}" : "{file} foi eliminado por vostede",
- "You deleted an encrypted file in {file}" : "Vostede eliminou un ficheiro cifrado en {file}",
+ "You deleted {file}" : "{file} foi eliminado por Vde.",
+ "You deleted an encrypted file in {file}" : "Vde. eliminou un ficheiro cifrado en {file}",
"{user} deleted {file}" : "{file} foi eliminado por {user}",
"{user} deleted an encrypted file in {file}" : "{user} eliminou un ficheiro cifrado en {file}",
- "You restored {file}" : "{file} foi restaurado por vostede",
+ "You restored {file}" : "{file} foi restaurado por Vde.",
"{user} restored {file}" : "{file} foi restaurado por {user}",
- "You renamed {oldfile} (hidden) to {newfile} (hidden)" : "Vostede renomeou {oldfile} (agochado) como {newfile} (agochado)",
- "You renamed {oldfile} (hidden) to {newfile}" : "Vostede renomeou {oldfile} (agochado) como {newfile}",
- "You renamed {oldfile} to {newfile} (hidden)" : "Vostede renomeou {oldfile} como {newfile} (agochado)",
- "You renamed {oldfile} to {newfile}" : "Vostede renomeou {oldfile} como {newfile}",
+ "You renamed {oldfile} (hidden) to {newfile} (hidden)" : "Vde. renomeou {oldfile} (agochado) como {newfile} (agochado)",
+ "You renamed {oldfile} (hidden) to {newfile}" : "Vde. renomeou {oldfile} (agochado) como {newfile}",
+ "You renamed {oldfile} to {newfile} (hidden)" : "Vde. renomeou {oldfile} como {newfile} (agochado)",
+ "You renamed {oldfile} to {newfile}" : "Vde. renomeou {oldfile} como {newfile}",
"{user} renamed {oldfile} (hidden) to {newfile} (hidden)" : "{user} renomeou {oldfile} (agochado) como {newfile} (agochado)",
"{user} renamed {oldfile} (hidden) to {newfile}" : "{user} renomeou {oldfile} (agochado) como {newfile}",
"{user} renamed {oldfile} to {newfile} (hidden)" : "{user} renomeou {oldfile} como {newfile} (agochado)",
"{user} renamed {oldfile} to {newfile}" : "{user} renomeou {oldfile} como {newfile}",
- "You moved {oldfile} to {newfile}" : "Vostede moveu {oldfile} para {newfile}",
+ "You moved {oldfile} to {newfile}" : "Vde. moveu {oldfile} para {newfile}",
"{user} moved {oldfile} to {newfile}" : "{user} moveu {oldfile} para {newfile}",
"A file has been added to or removed from your <strong>favorites</strong>" : "Engadiuse ou retirouse un ficheiro dos seus <strong>favoritos</strong>",
"A file or folder has been <strong>changed</strong>" : "<strong>Cambiouse</strong> un ficheiro ou cartafol",
@@ -266,7 +266,7 @@ OC.L10N.register(
"Deleted files" : "Ficheiros eliminados",
"Shares" : "Comparticións",
"Shared with others" : "Compartido con outros",
- "Shared with you" : "Compartido con vostede",
+ "Shared with you" : "Compartido con Vde.",
"Shared by link" : "Compartido por ligazón",
"Deleted shares" : "Recursos compartidos eliminados",
"Pending shares" : "Comparticións pendentes",
diff --git a/apps/files/l10n/gl.json b/apps/files/l10n/gl.json
index 67389978d99..a2bc41020f7 100644
--- a/apps/files/l10n/gl.json
+++ b/apps/files/l10n/gl.json
@@ -124,30 +124,30 @@
"Renamed by {user}" : "Renomeado por {user}",
"Moved by {user}" : "Movido por {user}",
"\"remote user\"" : "«usuario remoto»",
- "You created {file}" : "{file} foi creado por vostede",
- "You created an encrypted file in {file}" : "Vostede creo un ficheiro cifrado en {file}",
+ "You created {file}" : "{file} foi creado por Vde.",
+ "You created an encrypted file in {file}" : "Vde. creo un ficheiro cifrado en {file}",
"{user} created {file}" : "{user} creou {file}",
"{user} created an encrypted file in {file}" : "{user} creou un ficheiro cifrado en {file}",
"{file} was created in a public folder" : "{file} foi creado nun cartafol público",
- "You changed {file}" : "{file} foi cambiado por vostede",
- "You changed an encrypted file in {file}" : "Vostede cambiou un ficheiro cifrado en {file}",
+ "You changed {file}" : "{file} foi cambiado por Vde.",
+ "You changed an encrypted file in {file}" : "Vde. cambiou un ficheiro cifrado en {file}",
"{user} changed {file}" : "{file} foi cambiado por {user}",
"{user} changed an encrypted file in {file}" : "{user} cambiou un ficheiro cifrado en {file}",
- "You deleted {file}" : "{file} foi eliminado por vostede",
- "You deleted an encrypted file in {file}" : "Vostede eliminou un ficheiro cifrado en {file}",
+ "You deleted {file}" : "{file} foi eliminado por Vde.",
+ "You deleted an encrypted file in {file}" : "Vde. eliminou un ficheiro cifrado en {file}",
"{user} deleted {file}" : "{file} foi eliminado por {user}",
"{user} deleted an encrypted file in {file}" : "{user} eliminou un ficheiro cifrado en {file}",
- "You restored {file}" : "{file} foi restaurado por vostede",
+ "You restored {file}" : "{file} foi restaurado por Vde.",
"{user} restored {file}" : "{file} foi restaurado por {user}",
- "You renamed {oldfile} (hidden) to {newfile} (hidden)" : "Vostede renomeou {oldfile} (agochado) como {newfile} (agochado)",
- "You renamed {oldfile} (hidden) to {newfile}" : "Vostede renomeou {oldfile} (agochado) como {newfile}",
- "You renamed {oldfile} to {newfile} (hidden)" : "Vostede renomeou {oldfile} como {newfile} (agochado)",
- "You renamed {oldfile} to {newfile}" : "Vostede renomeou {oldfile} como {newfile}",
+ "You renamed {oldfile} (hidden) to {newfile} (hidden)" : "Vde. renomeou {oldfile} (agochado) como {newfile} (agochado)",
+ "You renamed {oldfile} (hidden) to {newfile}" : "Vde. renomeou {oldfile} (agochado) como {newfile}",
+ "You renamed {oldfile} to {newfile} (hidden)" : "Vde. renomeou {oldfile} como {newfile} (agochado)",
+ "You renamed {oldfile} to {newfile}" : "Vde. renomeou {oldfile} como {newfile}",
"{user} renamed {oldfile} (hidden) to {newfile} (hidden)" : "{user} renomeou {oldfile} (agochado) como {newfile} (agochado)",
"{user} renamed {oldfile} (hidden) to {newfile}" : "{user} renomeou {oldfile} (agochado) como {newfile}",
"{user} renamed {oldfile} to {newfile} (hidden)" : "{user} renomeou {oldfile} como {newfile} (agochado)",
"{user} renamed {oldfile} to {newfile}" : "{user} renomeou {oldfile} como {newfile}",
- "You moved {oldfile} to {newfile}" : "Vostede moveu {oldfile} para {newfile}",
+ "You moved {oldfile} to {newfile}" : "Vde. moveu {oldfile} para {newfile}",
"{user} moved {oldfile} to {newfile}" : "{user} moveu {oldfile} para {newfile}",
"A file has been added to or removed from your <strong>favorites</strong>" : "Engadiuse ou retirouse un ficheiro dos seus <strong>favoritos</strong>",
"A file or folder has been <strong>changed</strong>" : "<strong>Cambiouse</strong> un ficheiro ou cartafol",
@@ -264,7 +264,7 @@
"Deleted files" : "Ficheiros eliminados",
"Shares" : "Comparticións",
"Shared with others" : "Compartido con outros",
- "Shared with you" : "Compartido con vostede",
+ "Shared with you" : "Compartido con Vde.",
"Shared by link" : "Compartido por ligazón",
"Deleted shares" : "Recursos compartidos eliminados",
"Pending shares" : "Comparticións pendentes",
diff --git a/apps/files/lib/Capabilities.php b/apps/files/lib/Capabilities.php
index 5cb976a47be..dc2aae6acfc 100644
--- a/apps/files/lib/Capabilities.php
+++ b/apps/files/lib/Capabilities.php
@@ -38,12 +38,14 @@ class Capabilities implements ICapability {
/**
* Return this classes capabilities
+ *
+ * @return array{files: array{bigfilechunking: bool, blacklisted_files: array<mixed>}}
*/
public function getCapabilities() {
return [
'files' => [
'bigfilechunking' => true,
- 'blacklisted_files' => $this->config->getSystemValue('blacklisted_files', ['.htaccess'])
+ 'blacklisted_files' => (array)$this->config->getSystemValue('blacklisted_files', ['.htaccess'])
],
];
}
diff --git a/apps/files/lib/Command/Copy.php b/apps/files/lib/Command/Copy.php
new file mode 100644
index 00000000000..678c82a138f
--- /dev/null
+++ b/apps/files/lib/Command/Copy.php
@@ -0,0 +1,133 @@
+<?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\Folder;
+use OCP\Files\File;
+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 Copy extends Command {
+ private FileUtils $fileUtils;
+
+ public function __construct(FileUtils $fileUtils) {
+ $this->fileUtils = $fileUtils;
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('files:copy')
+ ->setDescription('Copy a file or folder')
+ ->addArgument('source', InputArgument::REQUIRED, "Source file id or path")
+ ->addArgument('target', InputArgument::REQUIRED, "Target path")
+ ->addOption('force', 'f', InputOption::VALUE_NONE, "Don't ask for confirmation and don't output any warnings")
+ ->addOption('no-target-directory', 'T', InputOption::VALUE_NONE, "When target path is folder, overwrite the folder instead of copying into the folder");
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $sourceInput = $input->getArgument('source');
+ $targetInput = $input->getArgument('target');
+ $force = $input->getOption('force');
+ $noTargetDir = $input->getOption('no-target-directory');
+
+ $node = $this->fileUtils->getNode($sourceInput);
+ $targetNode = $this->fileUtils->getNode($targetInput);
+
+ if (!$node) {
+ $output->writeln("<error>file $sourceInput not found</error>");
+ return 1;
+ }
+
+ $targetParentPath = dirname(rtrim($targetInput, '/'));
+ $targetParent = $this->fileUtils->getNode($targetParentPath);
+ if (!$targetParent) {
+ $output->writeln("<error>Target parent path $targetParentPath doesn't exist</error>");
+ return 1;
+ }
+
+ $wouldRequireDelete = false;
+
+ if ($targetNode) {
+ if (!$targetNode->isUpdateable()) {
+ $output->writeln("<error>$targetInput isn't writable</error>");
+ return 1;
+ }
+
+ if ($targetNode instanceof Folder) {
+ if ($noTargetDir) {
+ if (!$force) {
+ $output->writeln("Warning: <info>$sourceInput</info> is a file, but <info>$targetInput</info> is a folder");
+ }
+ $wouldRequireDelete = true;
+ } else {
+ $targetInput = $targetNode->getFullPath($node->getName());
+ $targetNode = $this->fileUtils->getNode($targetInput);
+ }
+ } else {
+ if ($node instanceof Folder) {
+ if (!$force) {
+ $output->writeln("Warning: <info>$sourceInput</info> is a folder, but <info>$targetInput</info> is a file");
+ }
+ $wouldRequireDelete = true;
+ }
+ }
+
+ if ($wouldRequireDelete && $targetNode->getInternalPath() === '') {
+ $output->writeln("<error>Mount root can't be overwritten with a different type</error>");
+ return 1;
+ }
+
+ if ($wouldRequireDelete && !$targetNode->isDeletable()) {
+ $output->writeln("<error>$targetInput can't be deleted to be replaced with $sourceInput</error>");
+ return 1;
+ }
+
+ if (!$force && $targetNode) {
+ /** @var QuestionHelper $helper */
+ $helper = $this->getHelper('question');
+
+ $question = new ConfirmationQuestion("<info>" . $targetInput . "</info> already exists, overwrite? [y/N] ", false);
+ if (!$helper->ask($input, $output, $question)) {
+ return 1;
+ }
+ }
+ }
+
+ if ($wouldRequireDelete && $targetNode) {
+ $targetNode->delete();
+ }
+
+ $node->copy($targetInput);
+
+ return 0;
+ }
+
+}
diff --git a/apps/files/lib/Command/Move.php b/apps/files/lib/Command/Move.php
new file mode 100644
index 00000000000..ac84dfa19b3
--- /dev/null
+++ b/apps/files/lib/Command/Move.php
@@ -0,0 +1,122 @@
+<?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\Folder;
+use OCP\Files\File;
+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 Move extends Command {
+ private FileUtils $fileUtils;
+
+ public function __construct(FileUtils $fileUtils) {
+ $this->fileUtils = $fileUtils;
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('files:move')
+ ->setDescription('Move a file or folder')
+ ->addArgument('source', InputArgument::REQUIRED, "Source file id or path")
+ ->addArgument('target', InputArgument::REQUIRED, "Target 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 {
+ $sourceInput = $input->getArgument('source');
+ $targetInput = $input->getArgument('target');
+ $force = $input->getOption('force');
+
+ $node = $this->fileUtils->getNode($sourceInput);
+ $targetNode = $this->fileUtils->getNode($targetInput);
+
+ if (!$node) {
+ $output->writeln("<error>file $sourceInput not found</error>");
+ return 1;
+ }
+
+ $targetParentPath = dirname(rtrim($targetInput, '/'));
+ $targetParent = $this->fileUtils->getNode($targetParentPath);
+ if (!$targetParent) {
+ $output->writeln("<error>Target parent path $targetParentPath doesn't exist</error>");
+ return 1;
+ }
+
+ $wouldRequireDelete = false;
+
+ if ($targetNode) {
+ if (!$targetNode->isUpdateable()) {
+ $output->writeln("<error>$targetInput already exists and isn't writable</error>");
+ return 1;
+ }
+
+ if ($node instanceof Folder && $targetNode instanceof File) {
+ $output->writeln("Warning: <info>$sourceInput</info> is a folder, but <info>$targetInput</info> is a file");
+ $wouldRequireDelete = true;
+ }
+
+ if ($node instanceof File && $targetNode instanceof Folder) {
+ $output->writeln("Warning: <info>$sourceInput</info> is a file, but <info>$targetInput</info> is a folder");
+ $wouldRequireDelete = true;
+ }
+
+ if ($wouldRequireDelete && $targetNode->getInternalPath() === '') {
+ $output->writeln("<error>Mount root can't be overwritten with a different type</error>");
+ return 1;
+ }
+
+ if ($wouldRequireDelete && !$targetNode->isDeletable()) {
+ $output->writeln("<error>$targetInput can't be deleted to be replaced with $sourceInput</error>");
+ return 1;
+ }
+
+ if (!$force) {
+ /** @var QuestionHelper $helper */
+ $helper = $this->getHelper('question');
+
+ $question = new ConfirmationQuestion("<info>" . $targetInput . "</info> already exists, overwrite? [y/N] ", false);
+ if (!$helper->ask($input, $output, $question)) {
+ return 1;
+ }
+ }
+ }
+
+ if ($wouldRequireDelete && $targetNode) {
+ $targetNode->delete();
+ }
+
+ $node->move($targetInput);
+
+ return 0;
+ }
+
+}
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
index f8911c4d104..3d490f06b75 100644
--- a/apps/files/lib/Controller/ApiController.php
+++ b/apps/files/lib/Controller/ApiController.php
@@ -60,8 +60,6 @@ use OCP\Share\IManager;
use OCP\Share\IShare;
/**
- * Class ApiController
- *
* @package OCA\Files\Controller
*/
class ApiController extends Controller {
@@ -104,10 +102,14 @@ class ApiController extends Controller {
* @NoCSRFRequired
* @StrictCookieRequired
*
- * @param int $x
- * @param int $y
+ * @param int $x Width of the thumbnail
+ * @param int $y Height of the thumbnail
* @param string $file URL-encoded filename
- * @return DataResponse|FileDisplayResponse
+ * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{message?: string}, array{}>
+ *
+ * 200: Thumbnail returned
+ * 400: Getting thumbnail is not possible
+ * 404: File not found
*/
public function getThumbnail($x, $y, $file) {
if ($x < 1 || $y < 1) {
@@ -386,6 +388,12 @@ class ApiController extends Controller {
/**
* @NoAdminRequired
* @NoCSRFRequired
+ *
+ * Get the service-worker Javascript for previews
+ *
+ * @psalm-suppress MoreSpecificReturnType The value of Service-Worker-Allowed is not relevant
+ * @psalm-suppress LessSpecificReturnStatement The value of Service-Worker-Allowed is not relevant
+ * @return StreamResponse<Http::STATUS_OK, array{Content-Type: 'application/javascript', Service-Worker-Allowed: string}>
*/
public function serviceWorker(): StreamResponse {
$response = new StreamResponse(__DIR__ . '/../../../../dist/preview-service-worker.js');
diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php
index 9b48d6958aa..d58be166e79 100644
--- a/apps/files/lib/Controller/DirectEditingController.php
+++ b/apps/files/lib/Controller/DirectEditingController.php
@@ -63,6 +63,9 @@ class DirectEditingController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Get the direct editing capabilities
+ * @return DataResponse<Http::STATUS_OK, array{editors: array<string, array{id: string, name: string, mimetypes: string[], optionalMimetypes: string[], secure: bool}>, creators: array<string, array{id: string, editor: string, name: string, extension: string, templates: bool, mimetypes: string[]}>}, array{}>
*/
public function info(): DataResponse {
$response = new DataResponse($this->directEditingService->getDirectEditingCapabilitites());
@@ -72,6 +75,18 @@ class DirectEditingController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Create a file for direct editing
+ *
+ * @param string $path Path of the file
+ * @param string $editorId ID of the editor
+ * @param string $creatorId ID of the creator
+ * @param ?string $templateId ID of the template
+ *
+ * @return DataResponse<Http::STATUS_OK, array{url: string}, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: URL for direct editing returned
+ * 403: Opening file is not allowed
*/
public function create(string $path, string $editorId, string $creatorId, string $templateId = null): DataResponse {
if (!$this->directEditingManager->isEnabled()) {
@@ -92,6 +107,17 @@ class DirectEditingController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Open a file for direct editing
+ *
+ * @param string $path Path of the file
+ * @param ?string $editorId ID of the editor
+ * @param ?int $fileId ID of the file
+ *
+ * @return DataResponse<Http::STATUS_OK, array{url: string}, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: URL for direct editing returned
+ * 403: Opening file is not allowed
*/
public function open(string $path, string $editorId = null, ?int $fileId = null): DataResponse {
if (!$this->directEditingManager->isEnabled()) {
@@ -114,6 +140,15 @@ class DirectEditingController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Get the templates for direct editing
+ *
+ * @param string $editorId ID of the editor
+ * @param string $creatorId ID of the creator
+ *
+ * @return DataResponse<Http::STATUS_OK, array{templates: array<string, array{id: string, title: string, preview: ?string, extension: string, mimetype: string}>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
+ *
+ * 200: Templates returned
*/
public function templates(string $editorId, string $creatorId): DataResponse {
if (!$this->directEditingManager->isEnabled()) {
diff --git a/apps/files/lib/Controller/DirectEditingViewController.php b/apps/files/lib/Controller/DirectEditingViewController.php
index 30d54d5ceb3..0741b58cfe9 100644
--- a/apps/files/lib/Controller/DirectEditingViewController.php
+++ b/apps/files/lib/Controller/DirectEditingViewController.php
@@ -24,6 +24,7 @@ namespace OCA\Files\Controller;
use Exception;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\IgnoreOpenAPI;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
use OCP\DirectEditing\IManager;
@@ -32,6 +33,7 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\ILogger;
use OCP\IRequest;
+#[IgnoreOpenAPI]
class DirectEditingViewController extends Controller {
/** @var IEventDispatcher */
diff --git a/apps/files/lib/Controller/OpenLocalEditorController.php b/apps/files/lib/Controller/OpenLocalEditorController.php
index 7d784196361..d9fb80f2d2b 100644
--- a/apps/files/lib/Controller/OpenLocalEditorController.php
+++ b/apps/files/lib/Controller/OpenLocalEditorController.php
@@ -70,6 +70,14 @@ class OpenLocalEditorController extends OCSController {
/**
* @NoAdminRequired
* @UserRateThrottle(limit=10, period=120)
+ *
+ * Create a local editor
+ *
+ * @param string $path Path of the file
+ *
+ * @return DataResponse<Http::STATUS_OK, array{userId: ?string, pathHash: string, expirationTime: int, token: string}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
+ *
+ * 200: Local editor returned
*/
public function create(string $path): DataResponse {
$pathHash = sha1($path);
@@ -107,6 +115,16 @@ class OpenLocalEditorController extends OCSController {
/**
* @NoAdminRequired
* @BruteForceProtection(action=openLocalEditor)
+ *
+ * Validate a local editor
+ *
+ * @param string $path Path of the file
+ * @param string $token Token of the local editor
+ *
+ * @return DataResponse<Http::STATUS_OK, array{userId: string, pathHash: string, expirationTime: int, token: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ *
+ * 200: Local editor validated successfully
+ * 404: Local editor not found
*/
public function validate(string $path, string $token): DataResponse {
$pathHash = sha1($path);
diff --git a/apps/files/lib/Controller/TemplateController.php b/apps/files/lib/Controller/TemplateController.php
index d04d86760e6..645350010ec 100644
--- a/apps/files/lib/Controller/TemplateController.php
+++ b/apps/files/lib/Controller/TemplateController.php
@@ -26,13 +26,21 @@ declare(strict_types=1);
*/
namespace OCA\Files\Controller;
+use OCA\Files\ResponseDefinitions;
+use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
use OCP\Files\GenericFileException;
use OCP\Files\Template\ITemplateManager;
+use OCP\Files\Template\TemplateFileCreator;
use OCP\IRequest;
+/**
+ * @psalm-import-type FilesTemplate from ResponseDefinitions
+ * @psalm-import-type FilesTemplateFile from ResponseDefinitions
+ * @psalm-import-type FilesTemplateFileCreator from ResponseDefinitions
+ */
class TemplateController extends OCSController {
protected $templateManager;
@@ -43,6 +51,10 @@ class TemplateController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * List the available templates
+ *
+ * @return DataResponse<Http::STATUS_OK, array<FilesTemplateFileCreator>, array{}>
*/
public function list(): DataResponse {
return new DataResponse($this->templateManager->listTemplates());
@@ -50,7 +62,17 @@ class TemplateController extends OCSController {
/**
* @NoAdminRequired
- * @throws OCSForbiddenException
+ *
+ * Create a template
+ *
+ * @param string $filePath Path of the file
+ * @param string $templatePath Name of the template
+ * @param string $templateType Type of the template
+ *
+ * @return DataResponse<Http::STATUS_OK, FilesTemplateFile, array{}>
+ * @throws OCSForbiddenException Creating template is not allowed
+ *
+ * 200: Template created successfully
*/
public function create(string $filePath, string $templatePath = '', string $templateType = 'user'): DataResponse {
try {
@@ -62,13 +84,24 @@ class TemplateController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Initialize the template directory
+ *
+ * @param string $templatePath Path of the template directory
+ * @param bool $copySystemTemplates Whether to copy the system templates to the template directory
+ *
+ * @return DataResponse<Http::STATUS_OK, array{template_path: string, templates: FilesTemplateFileCreator[]}, array{}>
+ * @throws OCSForbiddenException Initializing the template directory is not allowed
+ *
+ * 200: Template directory initialized successfully
*/
public function path(string $templatePath = '', bool $copySystemTemplates = false) {
try {
+ /** @var string $templatePath */
$templatePath = $this->templateManager->initializeTemplateDirectory($templatePath, null, $copySystemTemplates);
return new DataResponse([
'template_path' => $templatePath,
- 'templates' => $this->templateManager->listCreators()
+ 'templates' => array_map(fn(TemplateFileCreator $creator) => $creator->jsonSerialize(), $this->templateManager->listCreators()),
]);
} catch (\Exception $e) {
throw new OCSForbiddenException($e->getMessage());
diff --git a/apps/files/lib/Controller/TransferOwnershipController.php b/apps/files/lib/Controller/TransferOwnershipController.php
index 5abd65444bf..ce68b28349e 100644
--- a/apps/files/lib/Controller/TransferOwnershipController.php
+++ b/apps/files/lib/Controller/TransferOwnershipController.php
@@ -82,6 +82,17 @@ class TransferOwnershipController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Transfer the ownership to another user
+ *
+ * @param string $recipient Username of the recipient
+ * @param string $path Path of the file
+ *
+ * @return DataResponse<Http::STATUS_OK|Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN, array<empty>, array{}>
+ *
+ * 200: Ownership transferred successfully
+ * 400: Transferring ownership is not possible
+ * 403: Transferring ownership is not allowed
*/
public function transfer(string $recipient, string $path): DataResponse {
$recipientUser = $this->userManager->get($recipient);
@@ -127,6 +138,16 @@ class TransferOwnershipController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Accept an ownership transfer
+ *
+ * @param int $id ID of the ownership transfer
+ *
+ * @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ *
+ * 200: Ownership transfer accepted successfully
+ * 403: Accepting ownership transfer is not allowed
+ * 404: Ownership transfer not found
*/
public function accept(int $id): DataResponse {
try {
@@ -160,6 +181,16 @@ class TransferOwnershipController extends OCSController {
/**
* @NoAdminRequired
+ *
+ * Reject an ownership transfer
+ *
+ * @param int $id ID of the ownership transfer
+ *
+ * @return DataResponse<Http::STATUS_OK|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ *
+ * 200: Ownership transfer rejected successfully
+ * 403: Rejecting ownership transfer is not allowed
+ * 404: Ownership transfer not found
*/
public function reject(int $id): DataResponse {
try {
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php
index 43be43aa116..01f85a7c939 100644
--- a/apps/files/lib/Controller/ViewController.php
+++ b/apps/files/lib/Controller/ViewController.php
@@ -35,6 +35,7 @@
*/
namespace OCA\Files\Controller;
+use OC\AppFramework\Http;
use OCA\Files\Activity\Helper;
use OCA\Files\AppInfo\Application;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
@@ -44,6 +45,7 @@ use OCA\Files\Service\ViewConfig;
use OCA\Viewer\Event\LoadViewer;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Attribute\IgnoreOpenAPI;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
@@ -63,10 +65,9 @@ use OCP\IUserSession;
use OCP\Share\IManager;
/**
- * Class ViewController
- *
* @package OCA\Files\Controller
*/
+#[IgnoreOpenAPI]
class ViewController extends Controller {
private IURLGenerator $urlGenerator;
private IL10N $l10n;
diff --git a/apps/files/lib/DirectEditingCapabilities.php b/apps/files/lib/DirectEditingCapabilities.php
index 10c8e95105a..1bc00519ae8 100644
--- a/apps/files/lib/DirectEditingCapabilities.php
+++ b/apps/files/lib/DirectEditingCapabilities.php
@@ -38,6 +38,9 @@ class DirectEditingCapabilities implements ICapability, IInitialStateExcludedCap
$this->urlGenerator = $urlGenerator;
}
+ /**
+ * @return array{files: array{directEditing: array{url: string, etag: string, supportsFileId: bool}}}
+ */
public function getCapabilities() {
return [
'files' => [
diff --git a/apps/files/lib/ResponseDefinitions.php b/apps/files/lib/ResponseDefinitions.php
new file mode 100644
index 00000000000..8a27ec4bb2f
--- /dev/null
+++ b/apps/files/lib/ResponseDefinitions.php
@@ -0,0 +1,67 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
+ *
+ * @author Kate Döen <kate.doeen@nextcloud.com>
+ *
+ * @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;
+
+/**
+ * @psalm-type FilesTemplate = array{
+ * templateType: string,
+ * templateId: string,
+ * basename: string,
+ * etag: string,
+ * fileid: int,
+ * filename: string,
+ * lastmod: int,
+ * mime: string,
+ * size: int,
+ * type: string,
+ * hasPreview: bool,
+ * previewUrl: ?string,
+ * }
+ *
+ * @psalm-type FilesTemplateFile = array{
+ * basename: string,
+ * etag: string,
+ * fileid: int,
+ * filename: ?string,
+ * lastmod: int,
+ * mime: string,
+ * size: int,
+ * type: string,
+ * hasPreview: bool,
+ * }
+ *
+ * @psalm-type FilesTemplateFileCreator = array{
+ * app: string,
+ * label: string,
+ * extension: string,
+ * iconClass: ?string,
+ * mimetypes: string[],
+ * ratio: ?float,
+ * actionLabel: string,
+ * }
+ */
+class ResponseDefinitions {
+}
diff --git a/apps/files/openapi.json b/apps/files/openapi.json
new file mode 100644
index 00000000000..c8e9bb6ff41
--- /dev/null
+++ b/apps/files/openapi.json
@@ -0,0 +1,1904 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "files",
+ "version": "0.0.1",
+ "description": "File Management",
+ "license": {
+ "name": "agpl"
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "basic_auth": {
+ "type": "http",
+ "scheme": "basic"
+ },
+ "bearer_auth": {
+ "type": "http",
+ "scheme": "bearer"
+ }
+ },
+ "schemas": {
+ "Capabilities": {
+ "type": "object",
+ "properties": {
+ "files": {
+ "type": [
+ "object",
+ "object"
+ ],
+ "required": [
+ "bigfilechunking",
+ "blacklisted_files",
+ "directEditing"
+ ],
+ "properties": {
+ "bigfilechunking": {
+ "type": "boolean"
+ },
+ "blacklisted_files": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "directEditing": {
+ "type": "object",
+ "required": [
+ "url",
+ "etag",
+ "supportsFileId"
+ ],
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "etag": {
+ "type": "string"
+ },
+ "supportsFileId": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "OCSMeta": {
+ "type": "object",
+ "required": [
+ "status",
+ "statuscode"
+ ],
+ "properties": {
+ "status": {
+ "type": "string"
+ },
+ "statuscode": {
+ "type": "integer"
+ },
+ "message": {
+ "type": "string"
+ },
+ "totalitems": {
+ "type": "string"
+ },
+ "itemsperpage": {
+ "type": "string"
+ }
+ }
+ },
+ "Template": {
+ "type": "object",
+ "required": [
+ "templateType",
+ "templateId",
+ "basename",
+ "etag",
+ "fileid",
+ "filename",
+ "lastmod",
+ "mime",
+ "size",
+ "type",
+ "hasPreview",
+ "previewUrl"
+ ],
+ "properties": {
+ "templateType": {
+ "type": "string"
+ },
+ "templateId": {
+ "type": "string"
+ },
+ "basename": {
+ "type": "string"
+ },
+ "etag": {
+ "type": "string"
+ },
+ "fileid": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "filename": {
+ "type": "string"
+ },
+ "lastmod": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "mime": {
+ "type": "string"
+ },
+ "size": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "type": {
+ "type": "string"
+ },
+ "hasPreview": {
+ "type": "boolean"
+ },
+ "previewUrl": {
+ "type": "string",
+ "nullable": true
+ }
+ }
+ },
+ "TemplateFile": {
+ "type": "object",
+ "required": [
+ "basename",
+ "etag",
+ "fileid",
+ "filename",
+ "lastmod",
+ "mime",
+ "size",
+ "type",
+ "hasPreview"
+ ],
+ "properties": {
+ "basename": {
+ "type": "string"
+ },
+ "etag": {
+ "type": "string"
+ },
+ "fileid": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "filename": {
+ "type": "string",
+ "nullable": true
+ },
+ "lastmod": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "mime": {
+ "type": "string"
+ },
+ "size": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "type": {
+ "type": "string"
+ },
+ "hasPreview": {
+ "type": "boolean"
+ }
+ }
+ },
+ "TemplateFileCreator": {
+ "type": "object",
+ "required": [
+ "app",
+ "label",
+ "extension",
+ "iconClass",
+ "mimetypes",
+ "ratio",
+ "actionLabel"
+ ],
+ "properties": {
+ "app": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ },
+ "extension": {
+ "type": "string"
+ },
+ "iconClass": {
+ "type": "string",
+ "nullable": true
+ },
+ "mimetypes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "ratio": {
+ "type": "number",
+ "format": "float",
+ "nullable": true
+ },
+ "actionLabel": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "paths": {
+ "/index.php/apps/files/api/v1/thumbnail/{x}/{y}/{file}": {
+ "get": {
+ "operationId": "api-get-thumbnail",
+ "summary": "Gets a thumbnail of the specified file",
+ "tags": [
+ "api"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "x",
+ "in": "path",
+ "description": "Width of the thumbnail",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "name": "y",
+ "in": "path",
+ "description": "Height of the thumbnail",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "name": "file",
+ "in": "path",
+ "description": "URL-encoded filename",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^.+$"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Thumbnail returned",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Getting thumbnail is not possible",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "File not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/index.php/apps/files/preview-service-worker.js": {
+ "get": {
+ "operationId": "api-service-worker",
+ "summary": "Get the service-worker Javascript for previews",
+ "tags": [
+ "api"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "headers": {
+ "Service-Worker-Allowed": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
+ "content": {
+ "application/javascript": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/directEditing": {
+ "get": {
+ "operationId": "direct_editing-info",
+ "summary": "Get the direct editing capabilities",
+ "tags": [
+ "direct_editing"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "editors",
+ "creators"
+ ],
+ "properties": {
+ "editors": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "mimetypes",
+ "optionalMimetypes",
+ "secure"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "mimetypes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "optionalMimetypes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "secure": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "creators": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "id",
+ "editor",
+ "name",
+ "extension",
+ "templates",
+ "mimetypes"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "editor": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "extension": {
+ "type": "string"
+ },
+ "templates": {
+ "type": "boolean"
+ },
+ "mimetypes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/directEditing/templates/{editorId}/{creatorId}": {
+ "get": {
+ "operationId": "direct_editing-templates",
+ "summary": "Get the templates for direct editing",
+ "tags": [
+ "direct_editing"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "editorId",
+ "in": "path",
+ "description": "ID of the editor",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "creatorId",
+ "in": "path",
+ "description": "ID of the creator",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Templates returned",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "templates"
+ ],
+ "properties": {
+ "templates": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "id",
+ "title",
+ "preview",
+ "extension",
+ "mimetype"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "preview": {
+ "type": "string",
+ "nullable": true
+ },
+ "extension": {
+ "type": "string"
+ },
+ "mimetype": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/directEditing/open": {
+ "post": {
+ "operationId": "direct_editing-open",
+ "summary": "Open a file for direct editing",
+ "tags": [
+ "direct_editing"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "path",
+ "in": "query",
+ "description": "Path of the file",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "editorId",
+ "in": "query",
+ "description": "ID of the editor",
+ "schema": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "fileId",
+ "in": "query",
+ "description": "ID of the file",
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "nullable": true
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "URL for direct editing returned",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "url"
+ ],
+ "properties": {
+ "url": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Opening file is not allowed",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/directEditing/create": {
+ "post": {
+ "operationId": "direct_editing-create",
+ "summary": "Create a file for direct editing",
+ "tags": [
+ "direct_editing"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "path",
+ "in": "query",
+ "description": "Path of the file",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "editorId",
+ "in": "query",
+ "description": "ID of the editor",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "creatorId",
+ "in": "query",
+ "description": "ID of the creator",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "templateId",
+ "in": "query",
+ "description": "ID of the template",
+ "schema": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "URL for direct editing returned",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "url"
+ ],
+ "properties": {
+ "url": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Opening file is not allowed",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/templates": {
+ "get": {
+ "operationId": "template-list",
+ "summary": "List the available templates",
+ "tags": [
+ "template"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TemplateFileCreator"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/templates/create": {
+ "post": {
+ "operationId": "template-create",
+ "summary": "Create a template",
+ "tags": [
+ "template"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "filePath",
+ "in": "query",
+ "description": "Path of the file",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "templatePath",
+ "in": "query",
+ "description": "Name of the template",
+ "schema": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ {
+ "name": "templateType",
+ "in": "query",
+ "description": "Type of the template",
+ "schema": {
+ "type": "string",
+ "default": "user"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Template created successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/TemplateFile"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Creating template is not allowed",
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/templates/path": {
+ "post": {
+ "operationId": "template-path",
+ "summary": "Initialize the template directory",
+ "tags": [
+ "template"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "templatePath",
+ "in": "query",
+ "description": "Path of the template directory",
+ "schema": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ {
+ "name": "copySystemTemplates",
+ "in": "query",
+ "description": "Whether to copy the system templates to the template directory",
+ "schema": {
+ "type": "integer",
+ "default": 0
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Template directory initialized successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "template_path",
+ "templates"
+ ],
+ "properties": {
+ "template_path": {
+ "type": "string"
+ },
+ "templates": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TemplateFileCreator"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Initializing the template directory is not allowed",
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/transferownership": {
+ "post": {
+ "operationId": "transfer_ownership-transfer",
+ "summary": "Transfer the ownership to another user",
+ "tags": [
+ "transfer_ownership"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "recipient",
+ "in": "query",
+ "description": "Username of the recipient",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "path",
+ "in": "query",
+ "description": "Path of the file",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Ownership transferred successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Transferring ownership is not possible",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Transferring ownership is not allowed",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/transferownership/{id}": {
+ "post": {
+ "operationId": "transfer_ownership-accept",
+ "summary": "Accept an ownership transfer",
+ "tags": [
+ "transfer_ownership"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "ID of the ownership transfer",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Ownership transfer accepted successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Accepting ownership transfer is not allowed",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Ownership transfer not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "operationId": "transfer_ownership-reject",
+ "summary": "Reject an ownership transfer",
+ "tags": [
+ "transfer_ownership"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "description": "ID of the ownership transfer",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Ownership transfer rejected successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Rejecting ownership transfer is not allowed",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Ownership transfer not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/openlocaleditor": {
+ "post": {
+ "operationId": "open_local_editor-create",
+ "summary": "Create a local editor",
+ "tags": [
+ "open_local_editor"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "path",
+ "in": "query",
+ "description": "Path of the file",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Local editor returned",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "userId",
+ "pathHash",
+ "expirationTime",
+ "token"
+ ],
+ "properties": {
+ "userId": {
+ "type": "string",
+ "nullable": true
+ },
+ "pathHash": {
+ "type": "string"
+ },
+ "expirationTime": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "token": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/files/api/v1/openlocaleditor/{token}": {
+ "post": {
+ "operationId": "open_local_editor-validate",
+ "summary": "Validate a local editor",
+ "tags": [
+ "open_local_editor"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "path",
+ "in": "query",
+ "description": "Path of the file",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "description": "Token of the local editor",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "default": "true"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Local editor validated successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "userId",
+ "pathHash",
+ "expirationTime",
+ "token"
+ ],
+ "properties": {
+ "userId": {
+ "type": "string"
+ },
+ "pathHash": {
+ "type": "string"
+ },
+ "expirationTime": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "token": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Local editor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": []
+} \ No newline at end of file
diff --git a/apps/files_sharing/l10n/ar.js b/apps/files_sharing/l10n/ar.js
index ec7f6ecc2cd..81434e4d959 100644
--- a/apps/files_sharing/l10n/ar.js
+++ b/apps/files_sharing/l10n/ar.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "تنتهي في {relativetime}",
"this share just expired." : "هذه المشاركة انتهت للتو.",
"Shared with you by {owner}" : "شورك معك من قبل {owner}",
+ "_Accept share_::_Accept shares_" : ["قبول المشاركات","قبول المشاركة","قبول المشاركات","قبول المشاركات","قبول المشاركات","قبول المشاركات"],
+ "Open in files" : "فتح في الملفات",
+ "_Reject share_::_Reject shares_" : ["رفض المشاركات","رفض المشاركة","رفض المشاركات","رفض المشاركات","رفض المشاركات","رفض المشاركات"],
+ "_Restore share_::_Restore shares_" : ["استعادة المشاركات","استعادة المشاركة","استعادة المشاركات","استعادة المشاركات","استعادة المشاركات","استعادة المشاركات"],
"Link to a file" : "ارتباط إلى ملف",
"Error creating the share: {errorMessage}" : "خطأ في إنشاء المشاركة: {errorMessage}",
"Error creating the share" : "خطأ في إنشاء المشاركة",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "تمت مشاركته معك ومع المحادثة {conversation} بواسطة {owner}",
"Shared with you in a conversation by {owner}" : "تمت مشاركته معك في محادثة بواسطة {owner}",
"Shares" : "التي قمتَ بمشاركتها",
+ "Overview of shared files." : "استعراض الملفات التي تمت مشاركتها.",
"Shared with you" : "تم مشاركته معك",
+ "List of files that are shared with you." : "قائمة الملفات التي تمت مشاركتها معك.",
"Shared with others" : "مشترك مع الآخرين",
+ "List of files that you shared with others." : "قائمة الملفات التي شاركتها مع الآخرين.",
"Shared by link" : "مشاركة عن طريق رابط",
+ "List of files that are shared by link." : "قائمة الملفات التي تمت مشاركتها عن طريق رابط.",
"Deleted shares" : "مشاركة محذوفة",
+ "List of shares that you removed yourself from." : "قائمة المشاركات التي قمت بإزالة نفسك منها.",
"Pending shares" : "مشاركات معلقة",
+ "List of unapproved shares." : "قائمة المشاركات التي لم يتم الموافق عليها.",
"No entries found in this folder" : "لا يوجد مدخلات في هذا المجلد ",
"Name" : "اسم",
"Share time" : "وقت المشاركة",
diff --git a/apps/files_sharing/l10n/ar.json b/apps/files_sharing/l10n/ar.json
index 8d6a46c580b..3c460eb28cc 100644
--- a/apps/files_sharing/l10n/ar.json
+++ b/apps/files_sharing/l10n/ar.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "تنتهي في {relativetime}",
"this share just expired." : "هذه المشاركة انتهت للتو.",
"Shared with you by {owner}" : "شورك معك من قبل {owner}",
+ "_Accept share_::_Accept shares_" : ["قبول المشاركات","قبول المشاركة","قبول المشاركات","قبول المشاركات","قبول المشاركات","قبول المشاركات"],
+ "Open in files" : "فتح في الملفات",
+ "_Reject share_::_Reject shares_" : ["رفض المشاركات","رفض المشاركة","رفض المشاركات","رفض المشاركات","رفض المشاركات","رفض المشاركات"],
+ "_Restore share_::_Restore shares_" : ["استعادة المشاركات","استعادة المشاركة","استعادة المشاركات","استعادة المشاركات","استعادة المشاركات","استعادة المشاركات"],
"Link to a file" : "ارتباط إلى ملف",
"Error creating the share: {errorMessage}" : "خطأ في إنشاء المشاركة: {errorMessage}",
"Error creating the share" : "خطأ في إنشاء المشاركة",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "تمت مشاركته معك ومع المحادثة {conversation} بواسطة {owner}",
"Shared with you in a conversation by {owner}" : "تمت مشاركته معك في محادثة بواسطة {owner}",
"Shares" : "التي قمتَ بمشاركتها",
+ "Overview of shared files." : "استعراض الملفات التي تمت مشاركتها.",
"Shared with you" : "تم مشاركته معك",
+ "List of files that are shared with you." : "قائمة الملفات التي تمت مشاركتها معك.",
"Shared with others" : "مشترك مع الآخرين",
+ "List of files that you shared with others." : "قائمة الملفات التي شاركتها مع الآخرين.",
"Shared by link" : "مشاركة عن طريق رابط",
+ "List of files that are shared by link." : "قائمة الملفات التي تمت مشاركتها عن طريق رابط.",
"Deleted shares" : "مشاركة محذوفة",
+ "List of shares that you removed yourself from." : "قائمة المشاركات التي قمت بإزالة نفسك منها.",
"Pending shares" : "مشاركات معلقة",
+ "List of unapproved shares." : "قائمة المشاركات التي لم يتم الموافق عليها.",
"No entries found in this folder" : "لا يوجد مدخلات في هذا المجلد ",
"Name" : "اسم",
"Share time" : "وقت المشاركة",
diff --git a/apps/files_sharing/l10n/ca.js b/apps/files_sharing/l10n/ca.js
index 3c33805c126..343f7ddc492 100644
--- a/apps/files_sharing/l10n/ca.js
+++ b/apps/files_sharing/l10n/ca.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "Caduca {relativetime}",
"this share just expired." : "aquest element compartit acaba de caducar.",
"Shared with you by {owner}" : "{owner} l'ha compartit amb vós",
+ "_Accept share_::_Accept shares_" : ["Accepta l'element compartit","Accepta els elements compartits"],
+ "Open in files" : "Obre a Fitxers",
+ "_Reject share_::_Reject shares_" : ["Rebutja l'element compartit","Rebutja els elements compartits"],
+ "_Restore share_::_Restore shares_" : ["Restaura l'element compartit","Restaura els elements compartits"],
"Link to a file" : "Enllaç a un fitxer",
"Error creating the share: {errorMessage}" : "S'ha produït un error en crear l'element compartit: {errorMessage}",
"Error creating the share" : "S'ha produït un error en crear l'element compartit",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "{owner} l'ha compartit amb vós i amb la conversa {conversation}",
"Shared with you in a conversation by {owner}" : "{owner} l'ha compartit amb vós en una conversa",
"Shares" : "Element compartits",
+ "Overview of shared files." : "Informació general dels fitxers compartits.",
"Shared with you" : "Compartit amb vós",
+ "List of files that are shared with you." : "Llista de fitxers que s'han compartit amb vós.",
"Shared with others" : "Compartit amb altres",
+ "List of files that you shared with others." : "Llista de fitxers que heu compartit amb altres persones.",
"Shared by link" : "Compartit amb un enllaç",
+ "List of files that are shared by link." : "Llista de fitxers compartits mitjançant un enllaç.",
"Deleted shares" : "Suprimit",
+ "List of shares that you removed yourself from." : "Llista d'elements compartits dels quals us heu suprimit.",
"Pending shares" : "Pendent",
+ "List of unapproved shares." : "Llista d'elements compartits no aprovats.",
"No entries found in this folder" : "No hi ha cap entrada en aquesta carpeta",
"Name" : "Nom",
"Share time" : "Temps d'ús compartit",
diff --git a/apps/files_sharing/l10n/ca.json b/apps/files_sharing/l10n/ca.json
index 5e5241be6e1..4f07a6505e1 100644
--- a/apps/files_sharing/l10n/ca.json
+++ b/apps/files_sharing/l10n/ca.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "Caduca {relativetime}",
"this share just expired." : "aquest element compartit acaba de caducar.",
"Shared with you by {owner}" : "{owner} l'ha compartit amb vós",
+ "_Accept share_::_Accept shares_" : ["Accepta l'element compartit","Accepta els elements compartits"],
+ "Open in files" : "Obre a Fitxers",
+ "_Reject share_::_Reject shares_" : ["Rebutja l'element compartit","Rebutja els elements compartits"],
+ "_Restore share_::_Restore shares_" : ["Restaura l'element compartit","Restaura els elements compartits"],
"Link to a file" : "Enllaç a un fitxer",
"Error creating the share: {errorMessage}" : "S'ha produït un error en crear l'element compartit: {errorMessage}",
"Error creating the share" : "S'ha produït un error en crear l'element compartit",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "{owner} l'ha compartit amb vós i amb la conversa {conversation}",
"Shared with you in a conversation by {owner}" : "{owner} l'ha compartit amb vós en una conversa",
"Shares" : "Element compartits",
+ "Overview of shared files." : "Informació general dels fitxers compartits.",
"Shared with you" : "Compartit amb vós",
+ "List of files that are shared with you." : "Llista de fitxers que s'han compartit amb vós.",
"Shared with others" : "Compartit amb altres",
+ "List of files that you shared with others." : "Llista de fitxers que heu compartit amb altres persones.",
"Shared by link" : "Compartit amb un enllaç",
+ "List of files that are shared by link." : "Llista de fitxers compartits mitjançant un enllaç.",
"Deleted shares" : "Suprimit",
+ "List of shares that you removed yourself from." : "Llista d'elements compartits dels quals us heu suprimit.",
"Pending shares" : "Pendent",
+ "List of unapproved shares." : "Llista d'elements compartits no aprovats.",
"No entries found in this folder" : "No hi ha cap entrada en aquesta carpeta",
"Name" : "Nom",
"Share time" : "Temps d'ús compartit",
diff --git a/apps/files_sharing/l10n/cs.js b/apps/files_sharing/l10n/cs.js
index d5bffaf1387..2087cb03c06 100644
--- a/apps/files_sharing/l10n/cs.js
+++ b/apps/files_sharing/l10n/cs.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "Platnost končí {relativetime}",
"this share just expired." : "platnost tohoto sdílení právě skončila.",
"Shared with you by {owner}" : "S vámi sdílí {owner}",
+ "_Accept share_::_Accept shares_" : ["Přijmout sdílení","Přijmout sdílení","Přijmout sdílení","Přijmout sdílení"],
+ "Open in files" : "Otevřít v aplikaci Soubory",
+ "_Reject share_::_Reject shares_" : ["Odmítnout sdílení","Odmítnout sdílení","Odmítnout sdílení","Odmítnout sdílení"],
+ "_Restore share_::_Restore shares_" : ["Obnovit sdílení","Obnovit sdílení","Obnovit sdílení","Obnovit sdílení"],
"Link to a file" : "Odkaz na soubor",
"Error creating the share: {errorMessage}" : "Chyba při vytváření sdílení: {errorMessage}",
"Error creating the share" : "Chyba při vytváření sdílení",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "{owner} sdílí vám a konverzaci {conversation}",
"Shared with you in a conversation by {owner}" : " {owner} vám sdílí v konverzaci",
"Shares" : "Sdílení",
+ "Overview of shared files." : "Přehled nasdílených souborů",
"Shared with you" : "Sdíleno s vámi",
+ "List of files that are shared with you." : "Seznam souborů, které vám byly nasdíleny.",
"Shared with others" : "Sdíleno s ostatními",
+ "List of files that you shared with others." : "Seznam souborů, které jste nasdíleli ostatním.",
"Shared by link" : "Sdíleno prostřednictvím odkazu",
+ "List of files that are shared by link." : "Seznam souborů, které jsou nasdílené odkazem.",
"Deleted shares" : "Smazaná sdílení",
+ "List of shares that you removed yourself from." : "Seznam sdílení, od kterých jste se odebrali.",
"Pending shares" : "Čekající sdílení",
+ "List of unapproved shares." : "Seznam neschválených sdílení.",
"No entries found in this folder" : "V této složce nebylo nic nalezeno",
"Name" : "Název",
"Share time" : "Čas sdílení",
diff --git a/apps/files_sharing/l10n/cs.json b/apps/files_sharing/l10n/cs.json
index 317e502e1c4..6da47f41ac3 100644
--- a/apps/files_sharing/l10n/cs.json
+++ b/apps/files_sharing/l10n/cs.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "Platnost končí {relativetime}",
"this share just expired." : "platnost tohoto sdílení právě skončila.",
"Shared with you by {owner}" : "S vámi sdílí {owner}",
+ "_Accept share_::_Accept shares_" : ["Přijmout sdílení","Přijmout sdílení","Přijmout sdílení","Přijmout sdílení"],
+ "Open in files" : "Otevřít v aplikaci Soubory",
+ "_Reject share_::_Reject shares_" : ["Odmítnout sdílení","Odmítnout sdílení","Odmítnout sdílení","Odmítnout sdílení"],
+ "_Restore share_::_Restore shares_" : ["Obnovit sdílení","Obnovit sdílení","Obnovit sdílení","Obnovit sdílení"],
"Link to a file" : "Odkaz na soubor",
"Error creating the share: {errorMessage}" : "Chyba při vytváření sdílení: {errorMessage}",
"Error creating the share" : "Chyba při vytváření sdílení",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "{owner} sdílí vám a konverzaci {conversation}",
"Shared with you in a conversation by {owner}" : " {owner} vám sdílí v konverzaci",
"Shares" : "Sdílení",
+ "Overview of shared files." : "Přehled nasdílených souborů",
"Shared with you" : "Sdíleno s vámi",
+ "List of files that are shared with you." : "Seznam souborů, které vám byly nasdíleny.",
"Shared with others" : "Sdíleno s ostatními",
+ "List of files that you shared with others." : "Seznam souborů, které jste nasdíleli ostatním.",
"Shared by link" : "Sdíleno prostřednictvím odkazu",
+ "List of files that are shared by link." : "Seznam souborů, které jsou nasdílené odkazem.",
"Deleted shares" : "Smazaná sdílení",
+ "List of shares that you removed yourself from." : "Seznam sdílení, od kterých jste se odebrali.",
"Pending shares" : "Čekající sdílení",
+ "List of unapproved shares." : "Seznam neschválených sdílení.",
"No entries found in this folder" : "V této složce nebylo nic nalezeno",
"Name" : "Název",
"Share time" : "Čas sdílení",
diff --git a/apps/files_sharing/l10n/de_DE.js b/apps/files_sharing/l10n/de_DE.js
index 57f166333f8..18fbc95adca 100644
--- a/apps/files_sharing/l10n/de_DE.js
+++ b/apps/files_sharing/l10n/de_DE.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "Läuft {relativetime} ab",
"this share just expired." : "Diese Freigabe ist gerade abgelaufen.",
"Shared with you by {owner}" : "{owner} hat diese mit Ihnen geteilt",
+ "_Accept share_::_Accept shares_" : ["Freigabe akzeptieren","Freigaben akzeptieren"],
+ "Open in files" : "In Dateien-App öffnen",
+ "_Reject share_::_Reject shares_" : ["Freigabe ablehnen","Freigaben ablehnen"],
+ "_Restore share_::_Restore shares_" : ["Freigabe wiederherstellen","Freigaben wiederherstellen"],
"Link to a file" : "Mit einer Datei verknüpfen",
"Error creating the share: {errorMessage}" : "Fehler beim Erstellen der Freigabe: {errorMessage}",
"Error creating the share" : "Fehler beim Erstellen der Freigabe",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "{owner} hat diese mit Ihnen und der Unterhaltung {conversation} geteilt",
"Shared with you in a conversation by {owner}" : "{owner} hat diese mit Ihnen in einer Unterhaltung geteilt",
"Shares" : "Freigaben",
+ "Overview of shared files." : "Übersicht geteilter Dateien",
"Shared with you" : "Mit Ihnen geteilt",
+ "List of files that are shared with you." : "Liste der Dateien, die mit Ihnen geteilt wurden.",
"Shared with others" : "Von Ihnen geteilt",
+ "List of files that you shared with others." : "Liste der Dateien die Sie mit anderen geteilt haben.",
"Shared by link" : "Geteilt über einen Link",
+ "List of files that are shared by link." : "Liste der Dateien, die mittel Link geteilt wurden.",
"Deleted shares" : "Gelöschte Freigaben",
+ "List of shares that you removed yourself from." : "Liste der FReigaben, aus denen Sie sich selbst entfernt haben.",
"Pending shares" : "Ausstehende Freigaben",
+ "List of unapproved shares." : "Liste ungeprüfter Freigaben.",
"No entries found in this folder" : "Keine Einträge in diesem Ordner gefunden",
"Name" : "Name",
"Share time" : "Freigabezeitpunkt",
diff --git a/apps/files_sharing/l10n/de_DE.json b/apps/files_sharing/l10n/de_DE.json
index 8c2af43428c..462f39a4ebe 100644
--- a/apps/files_sharing/l10n/de_DE.json
+++ b/apps/files_sharing/l10n/de_DE.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "Läuft {relativetime} ab",
"this share just expired." : "Diese Freigabe ist gerade abgelaufen.",
"Shared with you by {owner}" : "{owner} hat diese mit Ihnen geteilt",
+ "_Accept share_::_Accept shares_" : ["Freigabe akzeptieren","Freigaben akzeptieren"],
+ "Open in files" : "In Dateien-App öffnen",
+ "_Reject share_::_Reject shares_" : ["Freigabe ablehnen","Freigaben ablehnen"],
+ "_Restore share_::_Restore shares_" : ["Freigabe wiederherstellen","Freigaben wiederherstellen"],
"Link to a file" : "Mit einer Datei verknüpfen",
"Error creating the share: {errorMessage}" : "Fehler beim Erstellen der Freigabe: {errorMessage}",
"Error creating the share" : "Fehler beim Erstellen der Freigabe",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "{owner} hat diese mit Ihnen und der Unterhaltung {conversation} geteilt",
"Shared with you in a conversation by {owner}" : "{owner} hat diese mit Ihnen in einer Unterhaltung geteilt",
"Shares" : "Freigaben",
+ "Overview of shared files." : "Übersicht geteilter Dateien",
"Shared with you" : "Mit Ihnen geteilt",
+ "List of files that are shared with you." : "Liste der Dateien, die mit Ihnen geteilt wurden.",
"Shared with others" : "Von Ihnen geteilt",
+ "List of files that you shared with others." : "Liste der Dateien die Sie mit anderen geteilt haben.",
"Shared by link" : "Geteilt über einen Link",
+ "List of files that are shared by link." : "Liste der Dateien, die mittel Link geteilt wurden.",
"Deleted shares" : "Gelöschte Freigaben",
+ "List of shares that you removed yourself from." : "Liste der FReigaben, aus denen Sie sich selbst entfernt haben.",
"Pending shares" : "Ausstehende Freigaben",
+ "List of unapproved shares." : "Liste ungeprüfter Freigaben.",
"No entries found in this folder" : "Keine Einträge in diesem Ordner gefunden",
"Name" : "Name",
"Share time" : "Freigabezeitpunkt",
diff --git a/apps/files_sharing/l10n/en_GB.js b/apps/files_sharing/l10n/en_GB.js
index bb05383c93e..58cd53b0672 100644
--- a/apps/files_sharing/l10n/en_GB.js
+++ b/apps/files_sharing/l10n/en_GB.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "Expires {relativetime}",
"this share just expired." : "this share just expired.",
"Shared with you by {owner}" : "Shared with you by {owner}",
+ "_Accept share_::_Accept shares_" : ["Accept share","Accept shares"],
+ "Open in files" : "Open in files",
+ "_Reject share_::_Reject shares_" : ["Reject share","Reject shares"],
+ "_Restore share_::_Restore shares_" : ["Restore share","Restore shares"],
"Link to a file" : "Link to a file",
"Error creating the share: {errorMessage}" : "Error creating the share: {errorMessage}",
"Error creating the share" : "Error creating the share",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "Shared with you and the conversation {conversation} by {owner}",
"Shared with you in a conversation by {owner}" : "Shared with you in a conversation by {owner}",
"Shares" : "Shares",
+ "Overview of shared files." : "Overview of shared files.",
"Shared with you" : "Shared with you",
+ "List of files that are shared with you." : "List of files that are shared with you.",
"Shared with others" : "Shared with others",
+ "List of files that you shared with others." : "List of files that you shared with others.",
"Shared by link" : "Shared by link",
+ "List of files that are shared by link." : "List of files that are shared by link.",
"Deleted shares" : "Deleted shares",
+ "List of shares that you removed yourself from." : "List of shares that you removed yourself from.",
"Pending shares" : "Pending shares",
+ "List of unapproved shares." : "List of unapproved shares.",
"No entries found in this folder" : "No entries found in this folder",
"Name" : "Name",
"Share time" : "Share time",
diff --git a/apps/files_sharing/l10n/en_GB.json b/apps/files_sharing/l10n/en_GB.json
index c9a2d5b1221..c59cc07c77f 100644
--- a/apps/files_sharing/l10n/en_GB.json
+++ b/apps/files_sharing/l10n/en_GB.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "Expires {relativetime}",
"this share just expired." : "this share just expired.",
"Shared with you by {owner}" : "Shared with you by {owner}",
+ "_Accept share_::_Accept shares_" : ["Accept share","Accept shares"],
+ "Open in files" : "Open in files",
+ "_Reject share_::_Reject shares_" : ["Reject share","Reject shares"],
+ "_Restore share_::_Restore shares_" : ["Restore share","Restore shares"],
"Link to a file" : "Link to a file",
"Error creating the share: {errorMessage}" : "Error creating the share: {errorMessage}",
"Error creating the share" : "Error creating the share",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "Shared with you and the conversation {conversation} by {owner}",
"Shared with you in a conversation by {owner}" : "Shared with you in a conversation by {owner}",
"Shares" : "Shares",
+ "Overview of shared files." : "Overview of shared files.",
"Shared with you" : "Shared with you",
+ "List of files that are shared with you." : "List of files that are shared with you.",
"Shared with others" : "Shared with others",
+ "List of files that you shared with others." : "List of files that you shared with others.",
"Shared by link" : "Shared by link",
+ "List of files that are shared by link." : "List of files that are shared by link.",
"Deleted shares" : "Deleted shares",
+ "List of shares that you removed yourself from." : "List of shares that you removed yourself from.",
"Pending shares" : "Pending shares",
+ "List of unapproved shares." : "List of unapproved shares.",
"No entries found in this folder" : "No entries found in this folder",
"Name" : "Name",
"Share time" : "Share time",
diff --git a/apps/files_sharing/l10n/gl.js b/apps/files_sharing/l10n/gl.js
index c3c17838b8d..db31feed3df 100644
--- a/apps/files_sharing/l10n/gl.js
+++ b/apps/files_sharing/l10n/gl.js
@@ -25,8 +25,8 @@ OC.L10N.register(
"{actor} shared with group {group}" : "{actor} compartiu co grupo {group}",
"{actor} removed share for group {group}" : "{actor} retirou o recurso compartido para o grupo {group}",
"Share for group {group} expired" : "Caducou a compartición polo grupo {group}",
- "You shared {file} with group {group}" : "Vostede compartiu {file} co grupo {group}",
- "You removed group {group} from {file}" : "Vostede retirou o grupo {group} de {file}",
+ "You shared {file} with group {group}" : "Vde. compartiu {file} co grupo {group}",
+ "You removed group {group} from {file}" : "Vde. retirou o grupo {group} de {file}",
"{actor} shared {file} with group {group}" : "{actor} compartiu {file} co grupo {group}",
"{actor} removed group {group} from {file}" : "{actor} retirou o grupo {group} de {file}",
"Share for file {file} with group {group} expired" : "Caducou a compartición do ficheiro {file} co grupo {group} ",
@@ -36,8 +36,8 @@ OC.L10N.register(
"{actor} shared as public link" : "{actor} compartiu como ligazón pública",
"{actor} removed public link" : "{actor} retirou a ligazón pública",
"Public link of {actor} expired" : "A ligazón pública de {actor} caducou",
- "You shared {file} as public link" : "Vostede compartiu {file} como ligazón pública",
- "You removed public link for {file}" : "Vostede retirou a ligazón pública de {file}",
+ "You shared {file} as public link" : "Vde. compartiu {file} como ligazón pública",
+ "You removed public link for {file}" : "Vde. retirou a ligazón pública de {file}",
"Public link expired for {file}" : "A ligazón pública para {file} caducou",
"{actor} shared {file} as public link" : "{actor} compartiu {file} como ligazón pública",
"{actor} removed public link for {file}" : "{actor} retirou a ligazón pública para {file}",
@@ -47,7 +47,7 @@ OC.L10N.register(
"You received a new remote share {file} from {user}" : "Recibiu unha nova compartición remota {file} de {user}",
"{user} accepted the remote share of {file}" : "{user} aceptou a compartición remotade {file}",
"{user} declined the remote share of {file}" : "{user} declinou a compartición remota de {file}",
- "{user} unshared {file} from you" : "{user} deixou de compartir {file} con vostede",
+ "{user} unshared {file} from you" : "{user} deixou de compartir {file} con Vde.",
"Shared with {user}" : "Compartido con {user}",
"Removed share for {user}" : "Retirar o compartido para {user}",
"You removed yourself" : "Retirou a sí mesmo",
@@ -58,14 +58,14 @@ OC.L10N.register(
"{actor} removed share" : "{actor} retirou o compartido",
"Share for {user} expired" : "Caducou a compartición por {user}",
"Share expired" : "Caducou a compartición",
- "You shared {file} with {user}" : "Vostede compartiu {file} con {user}",
- "You removed {user} from {file}" : "Vostede retirou a {user} de {file}",
- "You removed yourself from {file}" : "Vostede retirouse a sí mesmo de {file}",
+ "You shared {file} with {user}" : "Vde. compartiu {file} con {user}",
+ "You removed {user} from {file}" : "Vde. retirou a {user} de {file}",
+ "You removed yourself from {file}" : "Vde. retirouse a sí mesmo de {file}",
"{actor} removed themselves from {file}" : "{actor} foi retirado de {file}",
"{actor} shared {file} with {user}" : "{actor} compartiu {file} con {user}",
"{actor} removed {user} from {file}" : "{actor} retirou a {user} de {file}",
- "{actor} shared {file} with you" : "{actor} compartiu {file} con vostede",
- "{actor} removed you from the share named {file}" : "{actor} retirouno a vostede da compartición nomeada {file}",
+ "{actor} shared {file} with you" : "{actor} compartiu {file} con Vde.",
+ "{actor} removed you from the share named {file}" : "{actor} retirouno a Vde. da compartición nomeada {file}",
"Share for file {file} with {user} expired" : "Caducou a compartición do ficheiro {file} co usuario {user} ",
"Share for file {file} expired" : "Caducou a compartición do ficheiro {file}",
"A file or folder shared by mail or by public link was <strong>downloaded</strong>" : "Foi <strong>descargado</strong> un ficheiro ou cartafol compartido por correo ou ligazón pública",
@@ -90,7 +90,7 @@ OC.L10N.register(
"Please specify a valid federated user ID" : "Especifique un ID de usuario federado válido",
"Invalid date, date format must be YYYY-MM-DD" : "Data incorrecta, o formato da date debe ser AAAA-MM-DD",
"Please specify a valid federated group ID" : "Especifique un ID de grupo federado válido",
- "You cannot share to a Circle if the app is not enabled" : "Vostede non pode compartir para un círculo se a aplicación non esta activada",
+ "You cannot share to a Circle if the app is not enabled" : "Vde. non pode compartir para un círculo se a aplicación non esta activada",
"Please specify a valid circle" : "Especifique un círculo correcto",
"Sharing %s failed because the back end does not support room shares" : "Fallou a compartición de %s, xa que a infraestrutura non admite salas compartidas",
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Produciuse un erro ao compartir %s porque a infraestrutura non admite recursos compartidos de ScienceMesh",
@@ -110,8 +110,8 @@ OC.L10N.register(
"File sharing" : "Compartir ficheiros",
"Share will expire tomorrow" : "O recurso compartido caduca mañá",
"Your share of {node} will expire tomorrow" : "A súa compartición de {node} caduca mañá",
- "You received {share} as a share by {user}" : "Vostede recibiu {share} como un elemento compartido de {user}",
- "You received {share} to group {group} as a share by {user}" : "Vostede recibiu {share} no grupo {group} como un elemento compartido de {user}",
+ "You received {share} as a share by {user}" : "Vde. recibiu {share} como un elemento compartido de {user}",
+ "You received {share} to group {group} as a share by {user}" : "Vde. recibiu {share} no grupo {group} como un elemento compartido de {user}",
"Accept" : "Aceptar",
"Reject" : "Rexeitar",
"This application enables users to share files within Nextcloud. If enabled, the admin can choose which groups can share files. The applicable users can then share files and folders with other users and groups within Nextcloud. In addition, if the admin enables the share link feature, an external link can be used to share files with other users outside of Nextcloud. Admins can also enforce passwords, expirations dates, and enable server to server sharing via share links, as well as sharing from mobile devices.\nTurning the feature off removes shared files and folders on the server for all share recipients, and also on the sync clients and mobile apps. More information is available in the Nextcloud Documentation." : "Esta aplicación permítelle aos usuarios compartir ficheiros dentro de Nextcloud. Se o activa, o administrador pode escoller que grupos poden compartir ficheiros. Os usuarios implicados poderán compartir ficheiros e cartafoles con outros usuarios e grupos dentro de Nextcloud. Ademais, se o administrador activa a característica de ligazón compartida, pode empregarse unha ligazón externa para compartir ficheiros con outros usuarios fora doeNextcloud. Os administradores poden impor o uso de contrasinais ou datas de caducidade e activar a compartición de servidor a servidor mediante ligazóns compartidas, así como compartir dende dispositivos móbiles.\nDesactivar esta característica elimina os ficheiros compartidos e os cartafoles no servidor, para todos os receptores, e tamén dos clientes de sincronización e móbiles. Ten dispoñíbel máis información na documentación de Nextcloud.",
@@ -209,7 +209,11 @@ OC.L10N.register(
"Unable to load the shares list" : "Non é posíbel cargar a lista de recursos compartidos",
"Expires {relativetime}" : "Caducidades {relativetime}",
"this share just expired." : "acaba de caducar este recurso compartido.",
- "Shared with you by {owner}" : "Compartido con vostede por {owner}",
+ "Shared with you by {owner}" : "Compartido con Vde. por {owner}",
+ "_Accept share_::_Accept shares_" : ["Aceptar o recurso compartido","Aceptar os recursos compartidos"],
+ "Open in files" : "Abrir en ficheiros",
+ "_Reject share_::_Reject shares_" : ["Rexeitar o recurso compartido","Rexeitar os recursos compartidos"],
+ "_Restore share_::_Restore shares_" : ["Restaurar o recurso compartido","Restaurar os recursos compartidos"],
"Link to a file" : "Ligar a un ficheiro",
"Error creating the share: {errorMessage}" : "Produciuse un erro ao crear a compartición: {errorMessage}",
"Error creating the share" : "Produciuse un erro ao crear a compartición",
@@ -224,16 +228,22 @@ OC.L10N.register(
"Shared with" : "Compartido con",
"Password created successfully" : "O contrasinal foi creado correctamente",
"Error generating password from password policy" : "Produciuse un erro ao xerar o contrasinal a partir da directiva de contrasinais",
- "Shared with you and the group {group} by {owner}" : "Compartido con vostede e co grupo {group} por {owner}",
- "Shared with you and {circle} by {owner}" : "Compartido con vostede e {circle} por {owner}",
- "Shared with you and the conversation {conversation} by {owner}" : "Compartido con vostede e a conversa {conversation} por {owner}",
- "Shared with you in a conversation by {owner}" : "Compartido con vostede nunha conversa por {owner}",
+ "Shared with you and the group {group} by {owner}" : "Compartido con Vde. e co grupo {group} por {owner}",
+ "Shared with you and {circle} by {owner}" : "Compartido con Vde. e {circle} por {owner}",
+ "Shared with you and the conversation {conversation} by {owner}" : "Compartido con Vde. e a conversa {conversation} por {owner}",
+ "Shared with you in a conversation by {owner}" : "Compartido con Vde. nunha conversa por {owner}",
"Shares" : "Recursos compartidos",
- "Shared with you" : "Compartido con vostede",
+ "Overview of shared files." : "Vista xeral dos ficheiros compartidos.",
+ "Shared with you" : "Compartido con Vde.",
+ "List of files that are shared with you." : "Lista de ficheiros que se comparten con Vde.",
"Shared with others" : "Compartido con outros",
+ "List of files that you shared with others." : "Lista de ficheiros que compartiu Vde. con outros.",
"Shared by link" : "Compartido por ligazón",
+ "List of files that are shared by link." : "Lista de ficheiros que se comparten mediante ligazón.",
"Deleted shares" : "Recursos compartidos eliminados",
+ "List of shares that you removed yourself from." : "Lista de recursos compartidos retirados por Vde.",
"Pending shares" : "Recursos compartidos pendentes",
+ "List of unapproved shares." : "Lista de recursos compartidos non aprobados.",
"No entries found in this folder" : "Non se atoparon entradas neste cartafol",
"Name" : "Nome",
"Share time" : "Compartido hai",
@@ -252,8 +262,8 @@ OC.L10N.register(
"Uploaded files:" : "Ficheiros enviados:",
"By uploading files, you agree to the %1$sterms of service%2$s." : "Ao enviar ficheiros acepta as %1$s condicións do servizo %2$s.",
"Add to your Nextcloud" : "Engadir ao seu Nextcloud",
- "Nothing shared with you yet" : "Aínda non hai nada compartido con vostede.",
- "Files and folders others share with you will show up here" : "Os ficheiros e cartafoles que outros compartan con vostede amosaranse aquí",
+ "Nothing shared with you yet" : "Aínda non hai nada compartido con Vde.",
+ "Files and folders others share with you will show up here" : "Os ficheiros e cartafoles que outros compartan con Vde. amosaranse aquí",
"Nothing shared yet" : "Aínda non hai nada compartido",
"Files and folders you share will show up here" : "Os ficheiros e cartafoles que comparta amosaranse aquí",
"No shared links" : "Non hai ligazóns compartidas",
diff --git a/apps/files_sharing/l10n/gl.json b/apps/files_sharing/l10n/gl.json
index 6e03968b96c..f488997b54d 100644
--- a/apps/files_sharing/l10n/gl.json
+++ b/apps/files_sharing/l10n/gl.json
@@ -23,8 +23,8 @@
"{actor} shared with group {group}" : "{actor} compartiu co grupo {group}",
"{actor} removed share for group {group}" : "{actor} retirou o recurso compartido para o grupo {group}",
"Share for group {group} expired" : "Caducou a compartición polo grupo {group}",
- "You shared {file} with group {group}" : "Vostede compartiu {file} co grupo {group}",
- "You removed group {group} from {file}" : "Vostede retirou o grupo {group} de {file}",
+ "You shared {file} with group {group}" : "Vde. compartiu {file} co grupo {group}",
+ "You removed group {group} from {file}" : "Vde. retirou o grupo {group} de {file}",
"{actor} shared {file} with group {group}" : "{actor} compartiu {file} co grupo {group}",
"{actor} removed group {group} from {file}" : "{actor} retirou o grupo {group} de {file}",
"Share for file {file} with group {group} expired" : "Caducou a compartición do ficheiro {file} co grupo {group} ",
@@ -34,8 +34,8 @@
"{actor} shared as public link" : "{actor} compartiu como ligazón pública",
"{actor} removed public link" : "{actor} retirou a ligazón pública",
"Public link of {actor} expired" : "A ligazón pública de {actor} caducou",
- "You shared {file} as public link" : "Vostede compartiu {file} como ligazón pública",
- "You removed public link for {file}" : "Vostede retirou a ligazón pública de {file}",
+ "You shared {file} as public link" : "Vde. compartiu {file} como ligazón pública",
+ "You removed public link for {file}" : "Vde. retirou a ligazón pública de {file}",
"Public link expired for {file}" : "A ligazón pública para {file} caducou",
"{actor} shared {file} as public link" : "{actor} compartiu {file} como ligazón pública",
"{actor} removed public link for {file}" : "{actor} retirou a ligazón pública para {file}",
@@ -45,7 +45,7 @@
"You received a new remote share {file} from {user}" : "Recibiu unha nova compartición remota {file} de {user}",
"{user} accepted the remote share of {file}" : "{user} aceptou a compartición remotade {file}",
"{user} declined the remote share of {file}" : "{user} declinou a compartición remota de {file}",
- "{user} unshared {file} from you" : "{user} deixou de compartir {file} con vostede",
+ "{user} unshared {file} from you" : "{user} deixou de compartir {file} con Vde.",
"Shared with {user}" : "Compartido con {user}",
"Removed share for {user}" : "Retirar o compartido para {user}",
"You removed yourself" : "Retirou a sí mesmo",
@@ -56,14 +56,14 @@
"{actor} removed share" : "{actor} retirou o compartido",
"Share for {user} expired" : "Caducou a compartición por {user}",
"Share expired" : "Caducou a compartición",
- "You shared {file} with {user}" : "Vostede compartiu {file} con {user}",
- "You removed {user} from {file}" : "Vostede retirou a {user} de {file}",
- "You removed yourself from {file}" : "Vostede retirouse a sí mesmo de {file}",
+ "You shared {file} with {user}" : "Vde. compartiu {file} con {user}",
+ "You removed {user} from {file}" : "Vde. retirou a {user} de {file}",
+ "You removed yourself from {file}" : "Vde. retirouse a sí mesmo de {file}",
"{actor} removed themselves from {file}" : "{actor} foi retirado de {file}",
"{actor} shared {file} with {user}" : "{actor} compartiu {file} con {user}",
"{actor} removed {user} from {file}" : "{actor} retirou a {user} de {file}",
- "{actor} shared {file} with you" : "{actor} compartiu {file} con vostede",
- "{actor} removed you from the share named {file}" : "{actor} retirouno a vostede da compartición nomeada {file}",
+ "{actor} shared {file} with you" : "{actor} compartiu {file} con Vde.",
+ "{actor} removed you from the share named {file}" : "{actor} retirouno a Vde. da compartición nomeada {file}",
"Share for file {file} with {user} expired" : "Caducou a compartición do ficheiro {file} co usuario {user} ",
"Share for file {file} expired" : "Caducou a compartición do ficheiro {file}",
"A file or folder shared by mail or by public link was <strong>downloaded</strong>" : "Foi <strong>descargado</strong> un ficheiro ou cartafol compartido por correo ou ligazón pública",
@@ -88,7 +88,7 @@
"Please specify a valid federated user ID" : "Especifique un ID de usuario federado válido",
"Invalid date, date format must be YYYY-MM-DD" : "Data incorrecta, o formato da date debe ser AAAA-MM-DD",
"Please specify a valid federated group ID" : "Especifique un ID de grupo federado válido",
- "You cannot share to a Circle if the app is not enabled" : "Vostede non pode compartir para un círculo se a aplicación non esta activada",
+ "You cannot share to a Circle if the app is not enabled" : "Vde. non pode compartir para un círculo se a aplicación non esta activada",
"Please specify a valid circle" : "Especifique un círculo correcto",
"Sharing %s failed because the back end does not support room shares" : "Fallou a compartición de %s, xa que a infraestrutura non admite salas compartidas",
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Produciuse un erro ao compartir %s porque a infraestrutura non admite recursos compartidos de ScienceMesh",
@@ -108,8 +108,8 @@
"File sharing" : "Compartir ficheiros",
"Share will expire tomorrow" : "O recurso compartido caduca mañá",
"Your share of {node} will expire tomorrow" : "A súa compartición de {node} caduca mañá",
- "You received {share} as a share by {user}" : "Vostede recibiu {share} como un elemento compartido de {user}",
- "You received {share} to group {group} as a share by {user}" : "Vostede recibiu {share} no grupo {group} como un elemento compartido de {user}",
+ "You received {share} as a share by {user}" : "Vde. recibiu {share} como un elemento compartido de {user}",
+ "You received {share} to group {group} as a share by {user}" : "Vde. recibiu {share} no grupo {group} como un elemento compartido de {user}",
"Accept" : "Aceptar",
"Reject" : "Rexeitar",
"This application enables users to share files within Nextcloud. If enabled, the admin can choose which groups can share files. The applicable users can then share files and folders with other users and groups within Nextcloud. In addition, if the admin enables the share link feature, an external link can be used to share files with other users outside of Nextcloud. Admins can also enforce passwords, expirations dates, and enable server to server sharing via share links, as well as sharing from mobile devices.\nTurning the feature off removes shared files and folders on the server for all share recipients, and also on the sync clients and mobile apps. More information is available in the Nextcloud Documentation." : "Esta aplicación permítelle aos usuarios compartir ficheiros dentro de Nextcloud. Se o activa, o administrador pode escoller que grupos poden compartir ficheiros. Os usuarios implicados poderán compartir ficheiros e cartafoles con outros usuarios e grupos dentro de Nextcloud. Ademais, se o administrador activa a característica de ligazón compartida, pode empregarse unha ligazón externa para compartir ficheiros con outros usuarios fora doeNextcloud. Os administradores poden impor o uso de contrasinais ou datas de caducidade e activar a compartición de servidor a servidor mediante ligazóns compartidas, así como compartir dende dispositivos móbiles.\nDesactivar esta característica elimina os ficheiros compartidos e os cartafoles no servidor, para todos os receptores, e tamén dos clientes de sincronización e móbiles. Ten dispoñíbel máis información na documentación de Nextcloud.",
@@ -207,7 +207,11 @@
"Unable to load the shares list" : "Non é posíbel cargar a lista de recursos compartidos",
"Expires {relativetime}" : "Caducidades {relativetime}",
"this share just expired." : "acaba de caducar este recurso compartido.",
- "Shared with you by {owner}" : "Compartido con vostede por {owner}",
+ "Shared with you by {owner}" : "Compartido con Vde. por {owner}",
+ "_Accept share_::_Accept shares_" : ["Aceptar o recurso compartido","Aceptar os recursos compartidos"],
+ "Open in files" : "Abrir en ficheiros",
+ "_Reject share_::_Reject shares_" : ["Rexeitar o recurso compartido","Rexeitar os recursos compartidos"],
+ "_Restore share_::_Restore shares_" : ["Restaurar o recurso compartido","Restaurar os recursos compartidos"],
"Link to a file" : "Ligar a un ficheiro",
"Error creating the share: {errorMessage}" : "Produciuse un erro ao crear a compartición: {errorMessage}",
"Error creating the share" : "Produciuse un erro ao crear a compartición",
@@ -222,16 +226,22 @@
"Shared with" : "Compartido con",
"Password created successfully" : "O contrasinal foi creado correctamente",
"Error generating password from password policy" : "Produciuse un erro ao xerar o contrasinal a partir da directiva de contrasinais",
- "Shared with you and the group {group} by {owner}" : "Compartido con vostede e co grupo {group} por {owner}",
- "Shared with you and {circle} by {owner}" : "Compartido con vostede e {circle} por {owner}",
- "Shared with you and the conversation {conversation} by {owner}" : "Compartido con vostede e a conversa {conversation} por {owner}",
- "Shared with you in a conversation by {owner}" : "Compartido con vostede nunha conversa por {owner}",
+ "Shared with you and the group {group} by {owner}" : "Compartido con Vde. e co grupo {group} por {owner}",
+ "Shared with you and {circle} by {owner}" : "Compartido con Vde. e {circle} por {owner}",
+ "Shared with you and the conversation {conversation} by {owner}" : "Compartido con Vde. e a conversa {conversation} por {owner}",
+ "Shared with you in a conversation by {owner}" : "Compartido con Vde. nunha conversa por {owner}",
"Shares" : "Recursos compartidos",
- "Shared with you" : "Compartido con vostede",
+ "Overview of shared files." : "Vista xeral dos ficheiros compartidos.",
+ "Shared with you" : "Compartido con Vde.",
+ "List of files that are shared with you." : "Lista de ficheiros que se comparten con Vde.",
"Shared with others" : "Compartido con outros",
+ "List of files that you shared with others." : "Lista de ficheiros que compartiu Vde. con outros.",
"Shared by link" : "Compartido por ligazón",
+ "List of files that are shared by link." : "Lista de ficheiros que se comparten mediante ligazón.",
"Deleted shares" : "Recursos compartidos eliminados",
+ "List of shares that you removed yourself from." : "Lista de recursos compartidos retirados por Vde.",
"Pending shares" : "Recursos compartidos pendentes",
+ "List of unapproved shares." : "Lista de recursos compartidos non aprobados.",
"No entries found in this folder" : "Non se atoparon entradas neste cartafol",
"Name" : "Nome",
"Share time" : "Compartido hai",
@@ -250,8 +260,8 @@
"Uploaded files:" : "Ficheiros enviados:",
"By uploading files, you agree to the %1$sterms of service%2$s." : "Ao enviar ficheiros acepta as %1$s condicións do servizo %2$s.",
"Add to your Nextcloud" : "Engadir ao seu Nextcloud",
- "Nothing shared with you yet" : "Aínda non hai nada compartido con vostede.",
- "Files and folders others share with you will show up here" : "Os ficheiros e cartafoles que outros compartan con vostede amosaranse aquí",
+ "Nothing shared with you yet" : "Aínda non hai nada compartido con Vde.",
+ "Files and folders others share with you will show up here" : "Os ficheiros e cartafoles que outros compartan con Vde. amosaranse aquí",
"Nothing shared yet" : "Aínda non hai nada compartido",
"Files and folders you share will show up here" : "Os ficheiros e cartafoles que comparta amosaranse aquí",
"No shared links" : "Non hai ligazóns compartidas",
diff --git a/apps/files_sharing/l10n/sr.js b/apps/files_sharing/l10n/sr.js
index 2201090fcac..d3ee279799b 100644
--- a/apps/files_sharing/l10n/sr.js
+++ b/apps/files_sharing/l10n/sr.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "Истиче {relativetime}",
"this share just expired." : "ово дељење је управо истекло.",
"Shared with you by {owner}" : "{owner} је поделио са Вама",
+ "_Accept share_::_Accept shares_" : ["Прихвати дељење","Прихвати дељењa","Прихвати дељењa"],
+ "Open in files" : "Отвори у фајловима",
+ "_Reject share_::_Reject shares_" : ["Одбиј дељење","Одбиј дељења","Одбиј дељења"],
+ "_Restore share_::_Restore shares_" : ["Обнови дељење","Обнови дељења","Обнови дељења"],
"Link to a file" : "Веза ка фајлу",
"Error creating the share: {errorMessage}" : "Greška pri pravljenju deljenja: {errorMessage}",
"Error creating the share" : "Грешка при прављењу дељења",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "{owner} је поделио са Вама и разговором {conversation}",
"Shared with you in a conversation by {owner}" : "{owner} је поделио са Вама у разговору",
"Shares" : "Дељења",
+ "Overview of shared files." : "Преглед дељених фајлова.",
"Shared with you" : "Дељено са вама",
+ "List of files that are shared with you." : "Листа фајлова који су подељени са вама.",
"Shared with others" : "Дељено са осталима",
+ "List of files that you shared with others." : "Листа фајлова које сте поделили другима.",
"Shared by link" : "Дељено путем везе",
+ "List of files that are shared by link." : "Листа фајлова који су подељени линком.",
"Deleted shares" : "Обрисана дељења",
+ "List of shares that you removed yourself from." : "Листа дељења из којих сте уклонили себе.",
"Pending shares" : "Дељења на чекању",
+ "List of unapproved shares." : "Листа дељења која нису одобрена.",
"No entries found in this folder" : "Нема ничега у овој фасцикли",
"Name" : "Назив",
"Share time" : "Време дељења",
diff --git a/apps/files_sharing/l10n/sr.json b/apps/files_sharing/l10n/sr.json
index 3cf7df6989c..0129cd395d6 100644
--- a/apps/files_sharing/l10n/sr.json
+++ b/apps/files_sharing/l10n/sr.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "Истиче {relativetime}",
"this share just expired." : "ово дељење је управо истекло.",
"Shared with you by {owner}" : "{owner} је поделио са Вама",
+ "_Accept share_::_Accept shares_" : ["Прихвати дељење","Прихвати дељењa","Прихвати дељењa"],
+ "Open in files" : "Отвори у фајловима",
+ "_Reject share_::_Reject shares_" : ["Одбиј дељење","Одбиј дељења","Одбиј дељења"],
+ "_Restore share_::_Restore shares_" : ["Обнови дељење","Обнови дељења","Обнови дељења"],
"Link to a file" : "Веза ка фајлу",
"Error creating the share: {errorMessage}" : "Greška pri pravljenju deljenja: {errorMessage}",
"Error creating the share" : "Грешка при прављењу дељења",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "{owner} је поделио са Вама и разговором {conversation}",
"Shared with you in a conversation by {owner}" : "{owner} је поделио са Вама у разговору",
"Shares" : "Дељења",
+ "Overview of shared files." : "Преглед дељених фајлова.",
"Shared with you" : "Дељено са вама",
+ "List of files that are shared with you." : "Листа фајлова који су подељени са вама.",
"Shared with others" : "Дељено са осталима",
+ "List of files that you shared with others." : "Листа фајлова које сте поделили другима.",
"Shared by link" : "Дељено путем везе",
+ "List of files that are shared by link." : "Листа фајлова који су подељени линком.",
"Deleted shares" : "Обрисана дељења",
+ "List of shares that you removed yourself from." : "Листа дељења из којих сте уклонили себе.",
"Pending shares" : "Дељења на чекању",
+ "List of unapproved shares." : "Листа дељења која нису одобрена.",
"No entries found in this folder" : "Нема ничега у овој фасцикли",
"Name" : "Назив",
"Share time" : "Време дељења",
diff --git a/apps/files_sharing/l10n/sv.js b/apps/files_sharing/l10n/sv.js
index d5f8f28e012..63ab68cde2d 100644
--- a/apps/files_sharing/l10n/sv.js
+++ b/apps/files_sharing/l10n/sv.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "Upphör {relativetime}",
"this share just expired." : "denna delning har just gått ut.",
"Shared with you by {owner}" : "Delad med dig av {owner}",
+ "_Accept share_::_Accept shares_" : ["Acceptera delning","Acceptera delningar"],
+ "Open in files" : "Öppna i filer",
+ "_Reject share_::_Reject shares_" : ["Avvisa delning","Avvisa delningar"],
+ "_Restore share_::_Restore shares_" : ["Återställ delning","Återställ delningar"],
"Link to a file" : "Länka till en fil",
"Error creating the share: {errorMessage}" : "Kunde inte skapa delningen: {errorMessage}",
"Error creating the share" : "Kunde inte skapa delning",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "Delad med dig och konversation {conversation} av {owner}",
"Shared with you in a conversation by {owner}" : "Delad med dig i en konversation av {owner}",
"Shares" : "Delningar",
+ "Overview of shared files." : "Översikt över delade filer.",
"Shared with you" : "Delas med mig",
+ "List of files that are shared with you." : "Lista över filer som delas med dig.",
"Shared with others" : "Delas med andra",
+ "List of files that you shared with others." : "Lista över filer som du delat med andra.",
"Shared by link" : "Delat som länk",
+ "List of files that are shared by link." : "Lista över filer som delas via länk.",
"Deleted shares" : "Borttagna delningar",
+ "List of shares that you removed yourself from." : "Lista över delningar som du tagit bort dig själv från.",
"Pending shares" : "Väntande delningar",
+ "List of unapproved shares." : "Lista över ej godkända delningar.",
"No entries found in this folder" : "Inga filer hittades i denna mapp",
"Name" : "Namn",
"Share time" : "Delningstid",
diff --git a/apps/files_sharing/l10n/sv.json b/apps/files_sharing/l10n/sv.json
index 10043614b19..8b9d85796df 100644
--- a/apps/files_sharing/l10n/sv.json
+++ b/apps/files_sharing/l10n/sv.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "Upphör {relativetime}",
"this share just expired." : "denna delning har just gått ut.",
"Shared with you by {owner}" : "Delad med dig av {owner}",
+ "_Accept share_::_Accept shares_" : ["Acceptera delning","Acceptera delningar"],
+ "Open in files" : "Öppna i filer",
+ "_Reject share_::_Reject shares_" : ["Avvisa delning","Avvisa delningar"],
+ "_Restore share_::_Restore shares_" : ["Återställ delning","Återställ delningar"],
"Link to a file" : "Länka till en fil",
"Error creating the share: {errorMessage}" : "Kunde inte skapa delningen: {errorMessage}",
"Error creating the share" : "Kunde inte skapa delning",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "Delad med dig och konversation {conversation} av {owner}",
"Shared with you in a conversation by {owner}" : "Delad med dig i en konversation av {owner}",
"Shares" : "Delningar",
+ "Overview of shared files." : "Översikt över delade filer.",
"Shared with you" : "Delas med mig",
+ "List of files that are shared with you." : "Lista över filer som delas med dig.",
"Shared with others" : "Delas med andra",
+ "List of files that you shared with others." : "Lista över filer som du delat med andra.",
"Shared by link" : "Delat som länk",
+ "List of files that are shared by link." : "Lista över filer som delas via länk.",
"Deleted shares" : "Borttagna delningar",
+ "List of shares that you removed yourself from." : "Lista över delningar som du tagit bort dig själv från.",
"Pending shares" : "Väntande delningar",
+ "List of unapproved shares." : "Lista över ej godkända delningar.",
"No entries found in this folder" : "Inga filer hittades i denna mapp",
"Name" : "Namn",
"Share time" : "Delningstid",
diff --git a/apps/files_sharing/l10n/zh_HK.js b/apps/files_sharing/l10n/zh_HK.js
index e1228595517..f59ee40c684 100644
--- a/apps/files_sharing/l10n/zh_HK.js
+++ b/apps/files_sharing/l10n/zh_HK.js
@@ -210,6 +210,9 @@ OC.L10N.register(
"Expires {relativetime}" : "有效期至 {relativetime}",
"this share just expired." : "此分享剛過期。",
"Shared with you by {owner}" : "{owner} 已經和您分享",
+ "_Accept share_::_Accept shares_" : ["接受分享"],
+ "_Reject share_::_Reject shares_" : ["拒絕分享"],
+ "_Restore share_::_Restore shares_" : ["還原分享"],
"Link to a file" : "連結到一個檔案",
"Error creating the share: {errorMessage}" : "創建分享出錯:{errorMessage}",
"Error creating the share" : "創建分享出錯",
@@ -229,11 +232,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "{owner} 分享給您和 {conversation} 對話",
"Shared with you in a conversation by {owner}" : "{owner} 在對話中分享給您",
"Shares" : "分享",
+ "Overview of shared files." : "已分享檔案的概覽。",
"Shared with you" : "與您分享",
+ "List of files that are shared with you." : "與您分享的檔案清單。",
"Shared with others" : "與其他人分享",
+ "List of files that you shared with others." : "您與其他人分享的檔案清單。",
"Shared by link" : "由連結分享",
+ "List of files that are shared by link." : "透過連結分享的檔案清單。",
"Deleted shares" : "移除分享",
+ "List of shares that you removed yourself from." : "您將您自己從其中移除的分享清單。",
"Pending shares" : "等待分享",
+ "List of unapproved shares." : "未批准的分享清單。",
"No entries found in this folder" : "在此資料夾中沒有任何項目",
"Name" : "名稱",
"Share time" : "分享時間",
diff --git a/apps/files_sharing/l10n/zh_HK.json b/apps/files_sharing/l10n/zh_HK.json
index 534ee1f127a..9e0654311a3 100644
--- a/apps/files_sharing/l10n/zh_HK.json
+++ b/apps/files_sharing/l10n/zh_HK.json
@@ -208,6 +208,9 @@
"Expires {relativetime}" : "有效期至 {relativetime}",
"this share just expired." : "此分享剛過期。",
"Shared with you by {owner}" : "{owner} 已經和您分享",
+ "_Accept share_::_Accept shares_" : ["接受分享"],
+ "_Reject share_::_Reject shares_" : ["拒絕分享"],
+ "_Restore share_::_Restore shares_" : ["還原分享"],
"Link to a file" : "連結到一個檔案",
"Error creating the share: {errorMessage}" : "創建分享出錯:{errorMessage}",
"Error creating the share" : "創建分享出錯",
@@ -227,11 +230,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "{owner} 分享給您和 {conversation} 對話",
"Shared with you in a conversation by {owner}" : "{owner} 在對話中分享給您",
"Shares" : "分享",
+ "Overview of shared files." : "已分享檔案的概覽。",
"Shared with you" : "與您分享",
+ "List of files that are shared with you." : "與您分享的檔案清單。",
"Shared with others" : "與其他人分享",
+ "List of files that you shared with others." : "您與其他人分享的檔案清單。",
"Shared by link" : "由連結分享",
+ "List of files that are shared by link." : "透過連結分享的檔案清單。",
"Deleted shares" : "移除分享",
+ "List of shares that you removed yourself from." : "您將您自己從其中移除的分享清單。",
"Pending shares" : "等待分享",
+ "List of unapproved shares." : "未批准的分享清單。",
"No entries found in this folder" : "在此資料夾中沒有任何項目",
"Name" : "名稱",
"Share time" : "分享時間",
diff --git a/apps/files_sharing/l10n/zh_TW.js b/apps/files_sharing/l10n/zh_TW.js
index d4270593fcc..b8f2d5ff708 100644
--- a/apps/files_sharing/l10n/zh_TW.js
+++ b/apps/files_sharing/l10n/zh_TW.js
@@ -210,6 +210,10 @@ OC.L10N.register(
"Expires {relativetime}" : "過期於 {relativetime}",
"this share just expired." : "此分享剛過期。",
"Shared with you by {owner}" : "{owner} 已經和您分享",
+ "_Accept share_::_Accept shares_" : ["接受分享"],
+ "Open in files" : "在檔案中開啟",
+ "_Reject share_::_Reject shares_" : ["拒絕分享"],
+ "_Restore share_::_Restore shares_" : ["還原分享"],
"Link to a file" : "檔案連結",
"Error creating the share: {errorMessage}" : "建立分享時發生錯誤:{errorMessage}",
"Error creating the share" : "建立分享時發生錯誤",
@@ -229,11 +233,17 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "{owner} 分享給您和 {conversation} 對話",
"Shared with you in a conversation by {owner}" : "{owner} 在對話中分享給您",
"Shares" : "分享",
+ "Overview of shared files." : "已分享檔案的概覽。",
"Shared with you" : "與您分享",
+ "List of files that are shared with you." : "與您分享的檔案清單。",
"Shared with others" : "與其他人分享",
+ "List of files that you shared with others." : "您與其他人分享的檔案清單。",
"Shared by link" : "由連結分享",
+ "List of files that are shared by link." : "透過連結分享的檔案清單。",
"Deleted shares" : "已刪除的分享",
+ "List of shares that you removed yourself from." : "您將您自己從其中移除的分享清單。",
"Pending shares" : "擱置中的分享",
+ "List of unapproved shares." : "未批准的分享清單。",
"No entries found in this folder" : "在此資料夾中沒有任何項目",
"Name" : "名稱",
"Share time" : "分享時間",
diff --git a/apps/files_sharing/l10n/zh_TW.json b/apps/files_sharing/l10n/zh_TW.json
index 1d107dc37a1..2c5da3ad308 100644
--- a/apps/files_sharing/l10n/zh_TW.json
+++ b/apps/files_sharing/l10n/zh_TW.json
@@ -208,6 +208,10 @@
"Expires {relativetime}" : "過期於 {relativetime}",
"this share just expired." : "此分享剛過期。",
"Shared with you by {owner}" : "{owner} 已經和您分享",
+ "_Accept share_::_Accept shares_" : ["接受分享"],
+ "Open in files" : "在檔案中開啟",
+ "_Reject share_::_Reject shares_" : ["拒絕分享"],
+ "_Restore share_::_Restore shares_" : ["還原分享"],
"Link to a file" : "檔案連結",
"Error creating the share: {errorMessage}" : "建立分享時發生錯誤:{errorMessage}",
"Error creating the share" : "建立分享時發生錯誤",
@@ -227,11 +231,17 @@
"Shared with you and the conversation {conversation} by {owner}" : "{owner} 分享給您和 {conversation} 對話",
"Shared with you in a conversation by {owner}" : "{owner} 在對話中分享給您",
"Shares" : "分享",
+ "Overview of shared files." : "已分享檔案的概覽。",
"Shared with you" : "與您分享",
+ "List of files that are shared with you." : "與您分享的檔案清單。",
"Shared with others" : "與其他人分享",
+ "List of files that you shared with others." : "您與其他人分享的檔案清單。",
"Shared by link" : "由連結分享",
+ "List of files that are shared by link." : "透過連結分享的檔案清單。",
"Deleted shares" : "已刪除的分享",
+ "List of shares that you removed yourself from." : "您將您自己從其中移除的分享清單。",
"Pending shares" : "擱置中的分享",
+ "List of unapproved shares." : "未批准的分享清單。",
"No entries found in this folder" : "在此資料夾中沒有任何項目",
"Name" : "名稱",
"Share time" : "分享時間",
diff --git a/apps/files_trashbin/l10n/zh_CN.js b/apps/files_trashbin/l10n/zh_CN.js
index 7a3b9b23f37..06e276f9076 100644
--- a/apps/files_trashbin/l10n/zh_CN.js
+++ b/apps/files_trashbin/l10n/zh_CN.js
@@ -3,9 +3,11 @@ OC.L10N.register(
{
"restored" : "已恢复",
"Deleted files" : "已删除文件",
+ "Deleted files and folders in the trash bin (may expire during export if you are low on storage space)" : "回收站中已删除的文件与文件夹(如果您的存储空间不足,导出过程中可能会过期)",
"This application enables users to restore files that were deleted from the system." : "此应用允许用户恢复从系统中删除的文件。",
"This application enables users to restore files that were deleted from the system. It displays a list of deleted files in the web interface, and has options to restore those deleted files back to the users file directories or remove them permanently from the system. Restoring a file also restores related file versions, if the versions application is enabled. When a file is deleted from a share, it can be restored in the same manner, though it is no longer shared. By default, these files remain in the trash bin for 30 days.\nTo prevent a user from running out of disk space, the Deleted files app will not utilize more than 50% of the currently available free quota for deleted files. If the deleted files exceed this limit, the app deletes the oldest files until it gets below this limit. More information is available in the Deleted Files documentation." : "此应用允许用户恢复从系统中删除的文件。它会在 web 界面上显示已删除文件的列表,且可以选择将这些已删除文件恢复到用户的文件目录中或者将其永久地从系统删除。如果版本应用启用的话,恢复文件同时也会恢复相关的文件副本。当文件从共享中删除时,它可以用同样的方法恢复,但它不会再被共享。默认情况下,这些文件将会在回收站中保留 30 天。\n为防止用户把磁盘空间用完,文件删除应用用于保存已删除文件的空间将不会超过当前可用空闲配额的 50%。当已删除文件的容量超过这一限制时,应用将删除最旧的文件直到容量低于限制。更多信息请参考文件删除应用文档。",
"Restore" : "恢复",
+ "List of files that have been deleted." : "已被删除的文件清单",
"Deleted" : "已删除",
"No deleted files" : "无已删除文件",
"You will be able to recover deleted files from here" : "您可以在此处恢复已删除的文件",
diff --git a/apps/files_trashbin/l10n/zh_CN.json b/apps/files_trashbin/l10n/zh_CN.json
index 441827987ef..2009babed3e 100644
--- a/apps/files_trashbin/l10n/zh_CN.json
+++ b/apps/files_trashbin/l10n/zh_CN.json
@@ -1,9 +1,11 @@
{ "translations": {
"restored" : "已恢复",
"Deleted files" : "已删除文件",
+ "Deleted files and folders in the trash bin (may expire during export if you are low on storage space)" : "回收站中已删除的文件与文件夹(如果您的存储空间不足,导出过程中可能会过期)",
"This application enables users to restore files that were deleted from the system." : "此应用允许用户恢复从系统中删除的文件。",
"This application enables users to restore files that were deleted from the system. It displays a list of deleted files in the web interface, and has options to restore those deleted files back to the users file directories or remove them permanently from the system. Restoring a file also restores related file versions, if the versions application is enabled. When a file is deleted from a share, it can be restored in the same manner, though it is no longer shared. By default, these files remain in the trash bin for 30 days.\nTo prevent a user from running out of disk space, the Deleted files app will not utilize more than 50% of the currently available free quota for deleted files. If the deleted files exceed this limit, the app deletes the oldest files until it gets below this limit. More information is available in the Deleted Files documentation." : "此应用允许用户恢复从系统中删除的文件。它会在 web 界面上显示已删除文件的列表,且可以选择将这些已删除文件恢复到用户的文件目录中或者将其永久地从系统删除。如果版本应用启用的话,恢复文件同时也会恢复相关的文件副本。当文件从共享中删除时,它可以用同样的方法恢复,但它不会再被共享。默认情况下,这些文件将会在回收站中保留 30 天。\n为防止用户把磁盘空间用完,文件删除应用用于保存已删除文件的空间将不会超过当前可用空闲配额的 50%。当已删除文件的容量超过这一限制时,应用将删除最旧的文件直到容量低于限制。更多信息请参考文件删除应用文档。",
"Restore" : "恢复",
+ "List of files that have been deleted." : "已被删除的文件清单",
"Deleted" : "已删除",
"No deleted files" : "无已删除文件",
"You will be able to recover deleted files from here" : "您可以在此处恢复已删除的文件",
diff --git a/apps/files_trashbin/lib/Capabilities.php b/apps/files_trashbin/lib/Capabilities.php
index c0788ff7308..b53881daa29 100644
--- a/apps/files_trashbin/lib/Capabilities.php
+++ b/apps/files_trashbin/lib/Capabilities.php
@@ -33,6 +33,8 @@ class Capabilities implements ICapability {
/**
* Return this classes capabilities
+ *
+ * @return array{files: array{undelete: bool}}
*/
public function getCapabilities() {
return [
diff --git a/apps/files_trashbin/lib/Controller/PreviewController.php b/apps/files_trashbin/lib/Controller/PreviewController.php
index 9f60cc8b0b2..e62a793a98f 100644
--- a/apps/files_trashbin/lib/Controller/PreviewController.php
+++ b/apps/files_trashbin/lib/Controller/PreviewController.php
@@ -85,7 +85,18 @@ class PreviewController extends Controller {
* @NoAdminRequired
* @NoCSRFRequired
*
- * @return DataResponse|Http\FileDisplayResponse
+ * Get the preview for a file
+ *
+ * @param int $fileId ID of the file
+ * @param int $x Width of the preview
+ * @param int $y Height of the preview
+ * @param bool $a Whether to not crop the preview
+ *
+ * @return Http\FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ *
+ * 200: Preview returned
+ * 400: Getting preview is not possible
+ * 404: Preview not found
*/
public function getPreview(
int $fileId = -1,
diff --git a/apps/files_trashbin/openapi.json b/apps/files_trashbin/openapi.json
new file mode 100644
index 00000000000..a96cf5f189d
--- /dev/null
+++ b/apps/files_trashbin/openapi.json
@@ -0,0 +1,134 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "files_trashbin",
+ "version": "0.0.1",
+ "description": "This application enables users to restore files that were deleted from the system.",
+ "license": {
+ "name": "agpl"
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "basic_auth": {
+ "type": "http",
+ "scheme": "basic"
+ },
+ "bearer_auth": {
+ "type": "http",
+ "scheme": "bearer"
+ }
+ },
+ "schemas": {
+ "Capabilities": {
+ "type": "object",
+ "required": [
+ "files"
+ ],
+ "properties": {
+ "files": {
+ "type": "object",
+ "required": [
+ "undelete"
+ ],
+ "properties": {
+ "undelete": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "paths": {
+ "/index.php/apps/files_trashbin/preview": {
+ "get": {
+ "operationId": "preview-get-preview",
+ "summary": "Get the preview for a file",
+ "tags": [
+ "preview"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "fileId",
+ "in": "query",
+ "description": "ID of the file",
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "default": -1
+ }
+ },
+ {
+ "name": "x",
+ "in": "query",
+ "description": "Width of the preview",
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "default": 32
+ }
+ },
+ {
+ "name": "y",
+ "in": "query",
+ "description": "Height of the preview",
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "default": 32
+ }
+ },
+ {
+ "name": "a",
+ "in": "query",
+ "description": "Whether to not crop the preview",
+ "schema": {
+ "type": "integer",
+ "default": 0
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Preview returned",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Getting preview is not possible",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "404": {
+ "description": "Preview not found",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": []
+} \ No newline at end of file
diff --git a/apps/files_versions/l10n/zh_CN.js b/apps/files_versions/l10n/zh_CN.js
index 10659f9b7ff..5399984b8b6 100644
--- a/apps/files_versions/l10n/zh_CN.js
+++ b/apps/files_versions/l10n/zh_CN.js
@@ -3,14 +3,14 @@ OC.L10N.register(
{
"Versions" : "版本",
"This application automatically maintains older versions of files that are changed." : "此应用程序自动维护已更改文件的旧版本。",
- "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user's directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user does not run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user's currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "此应用程序自动维护已被更改的旧版本文件。开启时,每个用户的目录中都将呈现一个隐藏的版本文件夹,其用来储存旧版本文件。用户可随时通过Web界面还原到更旧的版本,使其成为新版本。此应用自动管理版本文件夹,以确保用户不因版本而用尽配额。\n除版本到期以外,版本App确保永不使用超过用户当前可用空间的50%。如果存储的版本超过此限制,此App将首先删除最旧的版本,直到符合限制。更多信息可见 版本 文档。",
+ "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user's directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user does not run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user's currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "此应用程序自动维护已被更改的旧版本文件。开启时,每个用户的目录中都将呈现一个隐藏的版本文件夹,其用来储存旧版本文件。用户可随时通过Web界面还原到更旧的版本,使其成为新版本。此应用自动管理版本文件夹,以确保用户不因版本而用尽配额。\n\t\t除版本到期以外,版本App确保永不使用超过用户当前可用空间的50%。如果存储的版本超过此限制,此App将首先删除最旧的版本,直到符合限制。更多信息可见 版本 文档。",
"Name this version" : "命名此版本",
"Edit version name" : "编辑版本名",
"Restore version" : "恢复版本",
"Download version" : "下载版本",
"Delete version" : "删除版本",
"Version name" : "版本名",
- "Named versions are persisted, and excluded from automatic cleanups when your storage quota is full." : "命名版本已保留,且当你的存储配额已满时,它将被从自动清理中排除。",
+ "Named versions are persisted, and excluded from automatic cleanups when your storage quota is full." : "命名版本会保留,且当你的存储配额已满时,它将被从自动清理中排除。",
"Remove version name" : "删除版本名",
"Save version name" : "保存版本名",
"Initial version restored" : "已还原最初版本",
@@ -18,7 +18,7 @@ OC.L10N.register(
"Could not restore version" : "无法还原版本",
"Could not set version name" : "无法设置版本名",
"Could not delete version" : "无法删除版本",
- "${version.label} restored" : "已还原版本 ${version.label} ",
+ "${version.label} restored" : "已还原版本${version.label} ",
"Failed to revert {file} to revision {timestamp}." : "将{file}还原为修订版{timestamp}失败。",
"_%n byte_::_%n bytes_" : ["%n 个字节"],
"Restore" : "恢复",
diff --git a/apps/files_versions/l10n/zh_CN.json b/apps/files_versions/l10n/zh_CN.json
index bac575b16fc..0025aa416eb 100644
--- a/apps/files_versions/l10n/zh_CN.json
+++ b/apps/files_versions/l10n/zh_CN.json
@@ -1,14 +1,14 @@
{ "translations": {
"Versions" : "版本",
"This application automatically maintains older versions of files that are changed." : "此应用程序自动维护已更改文件的旧版本。",
- "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user's directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user does not run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user's currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "此应用程序自动维护已被更改的旧版本文件。开启时,每个用户的目录中都将呈现一个隐藏的版本文件夹,其用来储存旧版本文件。用户可随时通过Web界面还原到更旧的版本,使其成为新版本。此应用自动管理版本文件夹,以确保用户不因版本而用尽配额。\n除版本到期以外,版本App确保永不使用超过用户当前可用空间的50%。如果存储的版本超过此限制,此App将首先删除最旧的版本,直到符合限制。更多信息可见 版本 文档。",
+ "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user's directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user does not run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user's currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "此应用程序自动维护已被更改的旧版本文件。开启时,每个用户的目录中都将呈现一个隐藏的版本文件夹,其用来储存旧版本文件。用户可随时通过Web界面还原到更旧的版本,使其成为新版本。此应用自动管理版本文件夹,以确保用户不因版本而用尽配额。\n\t\t除版本到期以外,版本App确保永不使用超过用户当前可用空间的50%。如果存储的版本超过此限制,此App将首先删除最旧的版本,直到符合限制。更多信息可见 版本 文档。",
"Name this version" : "命名此版本",
"Edit version name" : "编辑版本名",
"Restore version" : "恢复版本",
"Download version" : "下载版本",
"Delete version" : "删除版本",
"Version name" : "版本名",
- "Named versions are persisted, and excluded from automatic cleanups when your storage quota is full." : "命名版本已保留,且当你的存储配额已满时,它将被从自动清理中排除。",
+ "Named versions are persisted, and excluded from automatic cleanups when your storage quota is full." : "命名版本会保留,且当你的存储配额已满时,它将被从自动清理中排除。",
"Remove version name" : "删除版本名",
"Save version name" : "保存版本名",
"Initial version restored" : "已还原最初版本",
@@ -16,7 +16,7 @@
"Could not restore version" : "无法还原版本",
"Could not set version name" : "无法设置版本名",
"Could not delete version" : "无法删除版本",
- "${version.label} restored" : "已还原版本 ${version.label} ",
+ "${version.label} restored" : "已还原版本${version.label} ",
"Failed to revert {file} to revision {timestamp}." : "将{file}还原为修订版{timestamp}失败。",
"_%n byte_::_%n bytes_" : ["%n 个字节"],
"Restore" : "恢复",
diff --git a/apps/files_versions/lib/Capabilities.php b/apps/files_versions/lib/Capabilities.php
index b5861db8937..7091f9c4676 100644
--- a/apps/files_versions/lib/Capabilities.php
+++ b/apps/files_versions/lib/Capabilities.php
@@ -42,6 +42,8 @@ class Capabilities implements ICapability {
/**
* Return this classes capabilities
+ *
+ * @return array{files: array{versioning: bool, version_labeling: bool, version_deletion: bool}}
*/
public function getCapabilities() {
return [
diff --git a/apps/files_versions/lib/Controller/PreviewController.php b/apps/files_versions/lib/Controller/PreviewController.php
index 0e625dc2139..1365e7e50fa 100644
--- a/apps/files_versions/lib/Controller/PreviewController.php
+++ b/apps/files_versions/lib/Controller/PreviewController.php
@@ -69,11 +69,17 @@ class PreviewController extends Controller {
* @NoAdminRequired
* @NoCSRFRequired
*
- * @param string $file
- * @param int $x
- * @param int $y
- * @param string $version
- * @return DataResponse|FileDisplayResponse
+ * Get the preview for a file version
+ *
+ * @param string $file Path of the file
+ * @param int $x Width of the preview
+ * @param int $y Height of the preview
+ * @param string $version Version of the file to get the preview for
+ * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array<empty>, array{}>
+ *
+ * 200: Preview returned
+ * 400: Getting preview is not possible
+ * 404: Preview not found
*/
public function getPreview(
string $file = '',
diff --git a/apps/files_versions/openapi.json b/apps/files_versions/openapi.json
new file mode 100644
index 00000000000..2ff4dcc2035
--- /dev/null
+++ b/apps/files_versions/openapi.json
@@ -0,0 +1,141 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "files_versions",
+ "version": "0.0.1",
+ "description": "This application automatically maintains older versions of files that are changed.",
+ "license": {
+ "name": "agpl"
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "basic_auth": {
+ "type": "http",
+ "scheme": "basic"
+ },
+ "bearer_auth": {
+ "type": "http",
+ "scheme": "bearer"
+ }
+ },
+ "schemas": {
+ "Capabilities": {
+ "type": "object",
+ "required": [
+ "files"
+ ],
+ "properties": {
+ "files": {
+ "type": "object",
+ "required": [
+ "versioning",
+ "version_labeling",
+ "version_deletion"
+ ],
+ "properties": {
+ "versioning": {
+ "type": "boolean"
+ },
+ "version_labeling": {
+ "type": "boolean"
+ },
+ "version_deletion": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "paths": {
+ "/index.php/apps/files_versions/preview": {
+ "get": {
+ "operationId": "preview-get-preview",
+ "summary": "Get the preview for a file version",
+ "tags": [
+ "preview"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "file",
+ "in": "query",
+ "description": "Path of the file",
+ "schema": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ {
+ "name": "x",
+ "in": "query",
+ "description": "Width of the preview",
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "default": 44
+ }
+ },
+ {
+ "name": "y",
+ "in": "query",
+ "description": "Height of the preview",
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "default": 44
+ }
+ },
+ {
+ "name": "version",
+ "in": "query",
+ "description": "Version of the file to get the preview for",
+ "schema": {
+ "type": "string",
+ "default": ""
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Preview returned",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Getting preview is not possible",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ },
+ "404": {
+ "description": "Preview not found",
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": []
+} \ No newline at end of file
diff --git a/apps/settings/css/settings.css b/apps/settings/css/settings.css
index f0f798de98e..a24cbfca372 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-element);color:#fff;border-radius:var(--border-radius-pill);border:1px solid var(--color-primary-element);box-shadow:0 2px 9px var(--color-box-shadow)}.link-button:active,.link-button:hover,.link-button:focus{color:var(--color-primary-element);background-color:var(--color-primary-element-text);border-color:var(--color-primary-element) !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-element)}.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 .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) minmax(160px, 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,#body-settings #app-content.user-list-grid .row:not(.row--editable) .subAdminsGroups{overflow:auto;max-height:100%}#body-settings #app-content.user-list-grid .row .managers,#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 .subAdminsGroups,#body-settings #app-content.user-list-grid .row .quota{min-width:160px}#body-settings #app-content.user-list-grid .row .managers .select,#body-settings #app-content.user-list-grid .row .groups .select,#body-settings #app-content.user-list-grid .row .subadmins .select,#body-settings #app-content.user-list-grid .row .subAdminsGroups .select,#body-settings #app-content.user-list-grid .row .quota .select{width:100%;color:var(--color-text-dark);vertical-align:baseline}#body-settings #app-content.user-list-grid .row .managers progress,#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 .subAdminsGroups 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: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;align-items:center;justify-content:flex-end;height:100%;width:fit-content;padding-inline:12px;background-color:var(--color-main-background)}#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-element);color:#fff;border-radius:var(--border-radius-pill);border:1px solid var(--color-primary-element);box-shadow:0 2px 9px var(--color-box-shadow)}.link-button:active,.link-button:hover,.link-button:focus{color:var(--color-primary-element);background-color:var(--color-primary-element-text);border-color:var(--color-primary-element) !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-element)}.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 .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}.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 04c837346be..1ecef27f2d8 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,8CACA,WACA,wCACA,8CACA,6CAEA,0DAGC,mCACA,mDACA,qDAGD,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,oDAEA,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,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,+KASF,4CAEA,yDACC,WAID,iaAOC,UA1CkB,MA4ClB,ooCACC,6BACA,wBACA,uBAKD,odAMC,gBAMD,gPAGC,cACA,gBAIF,qSAKC,UA7EkB,MA+ElB,6UACC,WACA,6BACA,wBAGD,kVACC,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,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,mBACA,yBAGA,YACA,kBACA,oBACA,8CAKH,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,8CACA,WACA,wCACA,8CACA,6CAEA,0DAGC,mCACA,mDACA,qDAGD,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,oDAEA,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,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,WAGD,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 e80c20d39fc..d48a9576c15 100644
--- a/apps/settings/css/settings.scss
+++ b/apps/settings/css/settings.scss
@@ -1317,284 +1317,6 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
opacity: .7;
}
-
-/* USERS LIST -------------------------------------------------------------- */
-#body-settings {
- $grid-row-height: 60px;
- $grid-col-min-width: 160px;
-
- #app-content.user-list-grid {
- display: grid;
- grid-column-gap: 20px;
- grid-auto-rows: minmax(60px, max-content);
-
- .row {
- // TODO replace with css4 subgrid when available
- // fallback for ie11 no grid
- display: flex;
- display: grid;
- min-height: $grid-row-height;
- grid-row-start: span 1;
- grid-gap: 3px;
- align-items: center;
- /* let's define the column until storage path,
- what follows will be manually defined */
- grid-template-columns:
- 44px
- minmax($grid-col-min-width + 30px, 1fr) // username, displayname
- minmax($grid-col-min-width, 1fr) // password
- minmax($grid-col-min-width, 1fr) // email
- minmax(1.5*$grid-col-min-width, 1fr) // groups
- minmax(1.5*$grid-col-min-width, 1fr) // group admins
- minmax($grid-col-min-width, 1fr) // quota
- minmax(1.5*$grid-col-min-width, 1fr) // manager
- repeat(auto-fit, minmax($grid-col-min-width, 1fr));
- border-bottom: var(--color-border) 1px solid;
-
- &.disabled {
- opacity: .5;
- }
-
- /* grid col width */
- .name,
- .password,
- .mailAddress,
- .languages,
- .storageLocation,
- .userBackend,
- .lastLogin {
- min-width: $grid-col-min-width;
-
- doesnotexist:-o-prefocus, .strengthify-wrapper {
- color: var(--color-text-dark);
- vertical-align: baseline;
- text-overflow: ellipsis;
- }
- }
-
- &:not(.row--editable) {
- &.name,
- &.password,
- &.displayName,
- &.mailAddress,
- &.userBackend,
- &.languages {
- overflow: hidden;
- }
- }
-
- // Scroll if too much groups
- &:not(.row--editable) {
- .groups,
- .subadmins,
- .subAdminsGroups {
- overflow: auto;
- max-height: 100%;
- }
- }
-
- .managers,
- .groups,
- .subadmins,
- .subAdminsGroups,
- .quota {
- min-width: $grid-col-min-width;
-
- .select {
- width: 100%;
- color: var(--color-text-dark);
- vertical-align: baseline;
- }
-
- progress {
- max-width: 95%;
- }
- }
-
- .obfuscated {
- width: 400px;
- opacity: .7;
- }
-
- .userActions {
- display: flex;
- justify-content: flex-end;
- position: sticky;
- right: 0px;
- min-width: 88px;
- background-color: var(--color-main-background);
- }
-
- &.row--editable .userActions {
- z-index: 10;
- }
-
- .subtitle {
- color: var(--color-text-maxcontrast);
- vertical-align: baseline;
- }
-
- /* various */
- &#grid-header {
- position: sticky;
- align-self: normal;
- background-color: var(--color-main-background);
- z-index: 100; /* above multiselect */
- top: 0;
-
- &.sticky {
- box-shadow: 0 -2px 10px 1px var(--color-box-shadow);
- }
- }
-
- &#grid-header {
- color: var(--color-text-maxcontrast);
- border-bottom-width: thin;
-
- #headerDisplayName,
- #headerPassword,
- #headerAddress,
- #headerGroups,
- #headerSubAdmins,
- #theHeaderUserBackend,
- #theHeaderLastLogin,
- #headerQuota,
- #theHeaderStorageLocation,
- #headerLanguages {
- /* Line up header text with column content for when there’s inputs */
- padding-left: 7px;
- text-transform: none;
- color: var(--color-text-maxcontrast);
- vertical-align: baseline;
- }
- }
-
- &:hover {
- &:not(#grid-header) {
- box-shadow: 5px 0 0 var(--color-primary-element) inset;
- }
- }
-
- > form {
- width: 100%;
- }
-
- > div,
- > .displayName > form,
- > form {
- grid-row: 1;
- display: inline-flex;
- color: var(--color-text-lighter);
- flex-grow: 1;
-
- > input:not(:focus):not(:active) {
- border-color: transparent;
- cursor: pointer;
- }
-
- > input:focus, > input:active {
- + .icon-confirm {
- display: block !important;
- }
- }
-
- /* inputs like mail, username, password */
- &:not(.userActions) > input:not([type='submit']) {
- width: 100%;
- min-width: 0;
- }
-
- &.name {
- word-break: break-all;
- }
-
- &.displayName,
- &.mailAddress {
- > input {
- text-overflow: ellipsis;
- flex-grow: 1;
- }
- }
-
- &.name,
- &.userBackend {
- /* better multi-line visual */
- line-height: 1.3em;
- max-height: 100%;
- overflow: hidden;
- /* not supported by all browsers
- so we keep the overflow hidden
- as a fallback */
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- }
-
- &.name .subtitle {
- color: var(--color-main-text);
- }
-
- &.quota {
- display: flex;;
- justify-content: left;
- white-space: nowrap;
- position: relative;
-
- progress {
- width: 150px;
- margin-top: 35px;
- height: 3px;
- }
- }
-
- .icon-confirm {
- flex: 0 0 auto;
- cursor: pointer;
-
- &:not(:active) {
- display: none;
- }
- }
-
- &.avatar {
- height: 32px;
- width: 32px;
- margin: 6px;
-
- img {
- display: block;
- }
- }
-
- &.userActions {
- display: flex;
- align-items: center;
- justify-content: flex-end;
-
- // Make sure to cover whole row
- height: 100%;
- width: fit-content;
- padding-inline: 12px;
- background-color: var(--color-main-background);
- }
- }
- }
-
- .infinite-loading-container {
- display: flex;
- align-items: center;
- justify-content: center;
- grid-row-start: span 4;
- }
-
- .users-list-end {
- opacity: .5;
- user-select: none;
- }
- }
-}
-
.animated {
animation: blink-animation 1s steps(5, start) 4;
}
diff --git a/apps/settings/l10n/gl.js b/apps/settings/l10n/gl.js
index 4bd7d881aa8..e65ef7aa2c4 100644
--- a/apps/settings/l10n/gl.js
+++ b/apps/settings/l10n/gl.js
@@ -21,29 +21,29 @@ OC.L10N.register(
"Group list is empty" : "A lista de grupos está baleira",
"Unable to retrieve the group list" : "Non é posíbel recuperar a lista de grupos",
"{actor} added you to group {group}" : "{actor} engadiuno ao grupo {group}",
- "You added {user} to group {group}" : "Vostede engadiu a {actor} ao grupo {group}",
+ "You added {user} to group {group}" : "Vde. engadiu a {actor} ao grupo {group}",
"{actor} added {user} to group {group}" : "{actor} engadiu a {user} ao grupo {group}",
"An administrator added you to group {group}" : "Un administrador engadiuno ao grupo {group} ",
"An administrator added {user} to group {group}" : "Un administrador engadiu a {user} ao grupo {group}",
"{actor} removed you from group {group}" : "{actor} retirouno do grupo {group}",
- "You removed {user} from group {group}" : "Vostede retirou a {user} do grupo {group}",
+ "You removed {user} from group {group}" : "Vde. retirou a {user} do grupo {group}",
"{actor} removed {user} from group {group}" : "{actor} retirou a {user} do grupo {group}",
"An administrator removed you from group {group}" : "Un administrador retirouno do grupo {group} ",
"An administrator removed {user} from group {group}" : "Un administrador retirou a {user} do grupo {group}",
"Your <strong>group memberships</strong> were modified" : "Foron modificados os <strong>membros do seu grupo</strong>",
"{actor} changed your password" : "{actor} cambiou o seu contrasinal",
- "You changed your password" : "Vostede cambiou o seu contrasinal",
+ "You changed your password" : "Vde. cambiou o seu contrasinal",
"Your password was reset by an administrator" : "O seu contrasinal foi restabelecido por un administrador",
"Your password was reset" : "O seu contrasinal foi restabelecido",
"{actor} changed your email address" : "{actor} cambiou o seu enderezo de correo",
- "You changed your email address" : "Vostede cambiou o seu enderezo de correo",
+ "You changed your email address" : "Vde. cambiou o seu enderezo de correo",
"Your email address was changed by an administrator" : "O seu enderezo de correo foi cambiado por un administrador",
"You created an app password for a session named \"{token}\"" : "Creou un contrasinal de aplicación para unha sesión chamada «{token}»",
"An administrator created an app password for a session named \"{token}\"" : "Un administrador creou un contrasinal de aplicación para unha sesión chamada «{token}»",
- "You deleted app password \"{token}\"" : "Vostede eliminou o contrasinal da aplicación «{token}»",
- "You renamed app password \"{token}\" to \"{newToken}\"" : "Vostede renomeou o contrasinal da aplicación «{token}» como «{newToken}»",
- "You granted filesystem access to app password \"{token}\"" : "Vostede concedeu acceso ao sistema de ficheiros ao contrasinal da aplicación «{token}»",
- "You revoked filesystem access from app password \"{token}\"" : "Vostede revogou o acceso ao sistema de ficheiros dende o contrasinal da aplicación «{token}»",
+ "You deleted app password \"{token}\"" : "Vde. eliminou o contrasinal da aplicación «{token}»",
+ "You renamed app password \"{token}\" to \"{newToken}\"" : "Vde. renomeou o contrasinal da aplicación «{token}» como «{newToken}»",
+ "You granted filesystem access to app password \"{token}\"" : "Vde. concedeu acceso ao sistema de ficheiros ao contrasinal da aplicación «{token}»",
+ "You revoked filesystem access from app password \"{token}\"" : "Vde. revogou o acceso ao sistema de ficheiros dende o contrasinal da aplicación «{token}»",
"Security" : "Seguranza",
"You successfully logged in using two-factor authentication (%1$s)" : "Accedeu satisfactoriamente usando autenticación de dous factores (%1$s)",
"A login attempt using two-factor authentication failed (%1$s)" : "Fallou un intento de acceso usando autenticación de dous factores (%1$s)",
@@ -80,7 +80,7 @@ OC.L10N.register(
"Invalid SMTP password." : "Contrasinal SMTP incorrecta.",
"Email setting test" : "Proba do axuste do correo",
"Well done, %s!" : "Ben feito, %s!",
- "If you received this email, the email configuration seems to be correct." : "Se vostede ten recibido este correo, a configuración do correo semella ser correcta. ",
+ "If you received this email, the email configuration seems to be correct." : "Se Vde. ten recibido este correo, a configuración do correo semella ser correcta. ",
"Email could not be sent. Check your mail server log" : "Non foi posíbel enviar o correo. Comprobe o rexistro do servidor de correo",
"A problem occurred while sending the email. Please revise your settings. (Error: %s)" : "Ocorreu un problema ao enviar o correo. Revise os seus axustes. (Erro: %s)",
"You need to set your user email before being able to send test emails. Go to %s for that." : "É necesario estabelecer o seu correo de usuario antes de poder enviar correos de proba. Vaia a %spara iso.",
@@ -101,7 +101,7 @@ OC.L10N.register(
"Your password on %s was reset." : "Foi restabelecido o seu contrasinal en %s.",
"Password for %1$s changed on %2$s" : "Contrasinal para %1$s cambiado en %2$s",
"Password changed for %s" : "Contrasinal cambiado por %s",
- "If you did not request this, please contact an administrator." : "Se vostede non solicitou isto, póñase en contacto coa administración desta instancia.",
+ "If you did not request this, please contact an administrator." : "Se Vde. non solicitou isto, póñase en contacto coa administración desta instancia.",
"Your email address on %s was changed." : "Foi cambiado o seu enderezo de correo en %s.",
"Your email address on %s was changed by an administrator." : "O seu enderezo de correo en %s foi cambiado por un administrador",
"Email address for %1$s changed on %2$s" : "Enderezo de correo para %1$s cambiado en %2$s",
@@ -256,7 +256,7 @@ OC.L10N.register(
"Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "O cifrado por si só non garante a seguranza do sistema. Vexa a documentación para obter máis información sobre como funciona a aplicación de cifrado e os casos de uso admitidos.",
"Be aware that encryption always increases the file size." : "Teña presente que o cifrado sempre incrementa o tamaño do ficheiro.",
"It is always good to create regular backups of your data, in case of encryption make sure to backup the encryption keys along with your data." : "Sempre é bo crear copias de seguranza dos seus datos, no caso do cifrado, asegúrese de ter unha copia de seguranza das chaves de cifrado xunto cos seus datos.",
- "This is the final warning: Do you really want to enable encryption?" : "Esta é a advertencia final. Confirma que quere activar o cifrado?",
+ "This is the final warning: Do you really want to enable encryption?" : "Esta é a última advertencia: confirma que quere activar o cifrado?",
"No encryption module loaded, please enable an encryption module in the app menu." : "Non hai cargado ningún módulo de cifrado, active un módulo de cifrado no menú de aplicacións.",
"Select default encryption module:" : "Seleccionar o módulo predeterminado de cifrado:",
"You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please enable the \"Default encryption module\" and run {command}" : "É necesario migrar as súas chave de cifrado do antigo cifrado (ownCloud <= 8,0) cara ao novo. Active o «Módulo predeterminado de cifrado» e execute {command}",
@@ -286,7 +286,7 @@ OC.L10N.register(
"Error removing profile picture" : "Produciuse un erro ao retirar a imaxe de perfil",
"Your biography" : "A súa biografía",
"Details" : "Detalles",
- "You are a member of the following groups:" : "Vostede é membro dos seguintes grupos:",
+ "You are a member of the following groups:" : "Vde. é membro dos seguintes grupos:",
"You are using <strong>{usage}</strong>" : "Está a usar <strong>{usage}</strong>",
"You are using <strong>{usage}</strong> of <strong>{totalSpace}</strong> (<strong>{usageRelative}%</strong>)" : "Está a usar <strong>{usage}</strong> de <strong>{totalSpace}</strong> (<strong>{usageRelative}%</strong>)",
"Your full name" : "O seu nome completo",
@@ -363,7 +363,7 @@ OC.L10N.register(
"Add new user" : "Engadir un novo usuario",
"Username will be autogenerated" : "O nome de usuario vai ser xerado automaticamente",
"Username (required)" : "Nome de usuario (obrigatorio)",
- "You do not have permissions to see the details of this user" : "Vostede non ten permisos para ver os detalles deste usuario",
+ "You do not have permissions to see the details of this user" : "Vde. non ten permisos para ver os detalles deste usuario",
"Edit display name" : "Editar o nome para amosar",
"Add new password" : "Engadir un novo contrasinal",
"Add new email address" : "Engadir un novo enderezo de correo",
@@ -524,7 +524,7 @@ OC.L10N.register(
"This community release of Nextcloud is unsupported and instant notifications are unavailable." : "Esta versión comunitaria de Nextcloud non é compatíbel e as notificacións instantáneas non están dispoñíbeis.",
"Use a second factor besides your password to increase security for your account." : "Empregue un segundo factor ademais do seu contrasinal para aumentar a seguranza da súa conta.",
"If you use third party applications to connect to Nextcloud, please make sure to create and configure an app password for each before enabling second factor authentication." : " Se usa aplicacións de terceiros para conectarse a Nextcloud, asegúrese de crear e configurar un contrasinal de aplicación para cada unha antes de activar a autenticación de segundo factor.",
- "You created app password \"{token}\"" : "Vostede creou o contrasinal da aplicación «{token}»",
+ "You created app password \"{token}\"" : "Vde. creou o contrasinal da aplicación «{token}»",
"An administrator created app password \"{token}\"" : "Un administrador creou o contrasinal da aplicación «{token}»",
"Choose profile picture from files" : "Escolla a imaxe de perfil en ficheiros",
"png or jpg, max. 20 MB" : "png ou jpg, max. 20 MB",
diff --git a/apps/settings/l10n/gl.json b/apps/settings/l10n/gl.json
index fdbeac3bb21..1c295fbd7cb 100644
--- a/apps/settings/l10n/gl.json
+++ b/apps/settings/l10n/gl.json
@@ -19,29 +19,29 @@
"Group list is empty" : "A lista de grupos está baleira",
"Unable to retrieve the group list" : "Non é posíbel recuperar a lista de grupos",
"{actor} added you to group {group}" : "{actor} engadiuno ao grupo {group}",
- "You added {user} to group {group}" : "Vostede engadiu a {actor} ao grupo {group}",
+ "You added {user} to group {group}" : "Vde. engadiu a {actor} ao grupo {group}",
"{actor} added {user} to group {group}" : "{actor} engadiu a {user} ao grupo {group}",
"An administrator added you to group {group}" : "Un administrador engadiuno ao grupo {group} ",
"An administrator added {user} to group {group}" : "Un administrador engadiu a {user} ao grupo {group}",
"{actor} removed you from group {group}" : "{actor} retirouno do grupo {group}",
- "You removed {user} from group {group}" : "Vostede retirou a {user} do grupo {group}",
+ "You removed {user} from group {group}" : "Vde. retirou a {user} do grupo {group}",
"{actor} removed {user} from group {group}" : "{actor} retirou a {user} do grupo {group}",
"An administrator removed you from group {group}" : "Un administrador retirouno do grupo {group} ",
"An administrator removed {user} from group {group}" : "Un administrador retirou a {user} do grupo {group}",
"Your <strong>group memberships</strong> were modified" : "Foron modificados os <strong>membros do seu grupo</strong>",
"{actor} changed your password" : "{actor} cambiou o seu contrasinal",
- "You changed your password" : "Vostede cambiou o seu contrasinal",
+ "You changed your password" : "Vde. cambiou o seu contrasinal",
"Your password was reset by an administrator" : "O seu contrasinal foi restabelecido por un administrador",
"Your password was reset" : "O seu contrasinal foi restabelecido",
"{actor} changed your email address" : "{actor} cambiou o seu enderezo de correo",
- "You changed your email address" : "Vostede cambiou o seu enderezo de correo",
+ "You changed your email address" : "Vde. cambiou o seu enderezo de correo",
"Your email address was changed by an administrator" : "O seu enderezo de correo foi cambiado por un administrador",
"You created an app password for a session named \"{token}\"" : "Creou un contrasinal de aplicación para unha sesión chamada «{token}»",
"An administrator created an app password for a session named \"{token}\"" : "Un administrador creou un contrasinal de aplicación para unha sesión chamada «{token}»",
- "You deleted app password \"{token}\"" : "Vostede eliminou o contrasinal da aplicación «{token}»",
- "You renamed app password \"{token}\" to \"{newToken}\"" : "Vostede renomeou o contrasinal da aplicación «{token}» como «{newToken}»",
- "You granted filesystem access to app password \"{token}\"" : "Vostede concedeu acceso ao sistema de ficheiros ao contrasinal da aplicación «{token}»",
- "You revoked filesystem access from app password \"{token}\"" : "Vostede revogou o acceso ao sistema de ficheiros dende o contrasinal da aplicación «{token}»",
+ "You deleted app password \"{token}\"" : "Vde. eliminou o contrasinal da aplicación «{token}»",
+ "You renamed app password \"{token}\" to \"{newToken}\"" : "Vde. renomeou o contrasinal da aplicación «{token}» como «{newToken}»",
+ "You granted filesystem access to app password \"{token}\"" : "Vde. concedeu acceso ao sistema de ficheiros ao contrasinal da aplicación «{token}»",
+ "You revoked filesystem access from app password \"{token}\"" : "Vde. revogou o acceso ao sistema de ficheiros dende o contrasinal da aplicación «{token}»",
"Security" : "Seguranza",
"You successfully logged in using two-factor authentication (%1$s)" : "Accedeu satisfactoriamente usando autenticación de dous factores (%1$s)",
"A login attempt using two-factor authentication failed (%1$s)" : "Fallou un intento de acceso usando autenticación de dous factores (%1$s)",
@@ -78,7 +78,7 @@
"Invalid SMTP password." : "Contrasinal SMTP incorrecta.",
"Email setting test" : "Proba do axuste do correo",
"Well done, %s!" : "Ben feito, %s!",
- "If you received this email, the email configuration seems to be correct." : "Se vostede ten recibido este correo, a configuración do correo semella ser correcta. ",
+ "If you received this email, the email configuration seems to be correct." : "Se Vde. ten recibido este correo, a configuración do correo semella ser correcta. ",
"Email could not be sent. Check your mail server log" : "Non foi posíbel enviar o correo. Comprobe o rexistro do servidor de correo",
"A problem occurred while sending the email. Please revise your settings. (Error: %s)" : "Ocorreu un problema ao enviar o correo. Revise os seus axustes. (Erro: %s)",
"You need to set your user email before being able to send test emails. Go to %s for that." : "É necesario estabelecer o seu correo de usuario antes de poder enviar correos de proba. Vaia a %spara iso.",
@@ -99,7 +99,7 @@
"Your password on %s was reset." : "Foi restabelecido o seu contrasinal en %s.",
"Password for %1$s changed on %2$s" : "Contrasinal para %1$s cambiado en %2$s",
"Password changed for %s" : "Contrasinal cambiado por %s",
- "If you did not request this, please contact an administrator." : "Se vostede non solicitou isto, póñase en contacto coa administración desta instancia.",
+ "If you did not request this, please contact an administrator." : "Se Vde. non solicitou isto, póñase en contacto coa administración desta instancia.",
"Your email address on %s was changed." : "Foi cambiado o seu enderezo de correo en %s.",
"Your email address on %s was changed by an administrator." : "O seu enderezo de correo en %s foi cambiado por un administrador",
"Email address for %1$s changed on %2$s" : "Enderezo de correo para %1$s cambiado en %2$s",
@@ -254,7 +254,7 @@
"Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "O cifrado por si só non garante a seguranza do sistema. Vexa a documentación para obter máis información sobre como funciona a aplicación de cifrado e os casos de uso admitidos.",
"Be aware that encryption always increases the file size." : "Teña presente que o cifrado sempre incrementa o tamaño do ficheiro.",
"It is always good to create regular backups of your data, in case of encryption make sure to backup the encryption keys along with your data." : "Sempre é bo crear copias de seguranza dos seus datos, no caso do cifrado, asegúrese de ter unha copia de seguranza das chaves de cifrado xunto cos seus datos.",
- "This is the final warning: Do you really want to enable encryption?" : "Esta é a advertencia final. Confirma que quere activar o cifrado?",
+ "This is the final warning: Do you really want to enable encryption?" : "Esta é a última advertencia: confirma que quere activar o cifrado?",
"No encryption module loaded, please enable an encryption module in the app menu." : "Non hai cargado ningún módulo de cifrado, active un módulo de cifrado no menú de aplicacións.",
"Select default encryption module:" : "Seleccionar o módulo predeterminado de cifrado:",
"You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please enable the \"Default encryption module\" and run {command}" : "É necesario migrar as súas chave de cifrado do antigo cifrado (ownCloud <= 8,0) cara ao novo. Active o «Módulo predeterminado de cifrado» e execute {command}",
@@ -284,7 +284,7 @@
"Error removing profile picture" : "Produciuse un erro ao retirar a imaxe de perfil",
"Your biography" : "A súa biografía",
"Details" : "Detalles",
- "You are a member of the following groups:" : "Vostede é membro dos seguintes grupos:",
+ "You are a member of the following groups:" : "Vde. é membro dos seguintes grupos:",
"You are using <strong>{usage}</strong>" : "Está a usar <strong>{usage}</strong>",
"You are using <strong>{usage}</strong> of <strong>{totalSpace}</strong> (<strong>{usageRelative}%</strong>)" : "Está a usar <strong>{usage}</strong> de <strong>{totalSpace}</strong> (<strong>{usageRelative}%</strong>)",
"Your full name" : "O seu nome completo",
@@ -361,7 +361,7 @@
"Add new user" : "Engadir un novo usuario",
"Username will be autogenerated" : "O nome de usuario vai ser xerado automaticamente",
"Username (required)" : "Nome de usuario (obrigatorio)",
- "You do not have permissions to see the details of this user" : "Vostede non ten permisos para ver os detalles deste usuario",
+ "You do not have permissions to see the details of this user" : "Vde. non ten permisos para ver os detalles deste usuario",
"Edit display name" : "Editar o nome para amosar",
"Add new password" : "Engadir un novo contrasinal",
"Add new email address" : "Engadir un novo enderezo de correo",
@@ -522,7 +522,7 @@
"This community release of Nextcloud is unsupported and instant notifications are unavailable." : "Esta versión comunitaria de Nextcloud non é compatíbel e as notificacións instantáneas non están dispoñíbeis.",
"Use a second factor besides your password to increase security for your account." : "Empregue un segundo factor ademais do seu contrasinal para aumentar a seguranza da súa conta.",
"If you use third party applications to connect to Nextcloud, please make sure to create and configure an app password for each before enabling second factor authentication." : " Se usa aplicacións de terceiros para conectarse a Nextcloud, asegúrese de crear e configurar un contrasinal de aplicación para cada unha antes de activar a autenticación de segundo factor.",
- "You created app password \"{token}\"" : "Vostede creou o contrasinal da aplicación «{token}»",
+ "You created app password \"{token}\"" : "Vde. creou o contrasinal da aplicación «{token}»",
"An administrator created app password \"{token}\"" : "Un administrador creou o contrasinal da aplicación «{token}»",
"Choose profile picture from files" : "Escolla a imaxe de perfil en ficheiros",
"png or jpg, max. 20 MB" : "png ou jpg, max. 20 MB",
diff --git a/apps/settings/src/components/UserList.vue b/apps/settings/src/components/UserList.vue
index 2f3da92ca02..3d061a2f0d0 100644
--- a/apps/settings/src/components/UserList.vue
+++ b/apps/settings/src/components/UserList.vue
@@ -21,120 +21,90 @@
-->
<template>
- <div id="app-content"
- role="grid"
- :aria-label="t('settings', 'User\'s table')"
- class="user-list-grid"
- @scroll.passive="onScroll">
+ <Fragment>
<NewUserModal v-if="showConfig.showNewUserForm"
:loading="loading"
:new-user="newUser"
- :show-config="showConfig"
- @reset="resetForm"
- @close="showConfig.showNewUserForm = false" />
- <div id="grid-header"
- :class="{'sticky': scrolled && !showConfig.showNewUserForm}"
- class="row">
- <div id="headerAvatar" class="avatar" />
- <div id="headerName" class="name">
- <div class="subtitle">
- <strong>
- {{ t('settings', 'Display name') }}
- </strong>
- </div>
- {{ t('settings', 'Username') }}
- </div>
- <div id="headerPassword" class="password">
- {{ t('settings', 'Password') }}
- </div>
- <div id="headerAddress" class="mailAddress">
- {{ t('settings', 'Email') }}
- </div>
- <div id="headerGroups" class="groups">
- {{ t('settings', 'Groups') }}
- </div>
- <div v-if="subAdminsGroups.length>0 && settings.isAdmin"
- id="headerSubAdmins"
- class="subadmins">
- {{ t('settings', 'Group admin for') }}
- </div>
- <div id="headerQuota" class="quota">
- {{ t('settings', 'Quota') }}
- </div>
- <div v-if="showConfig.showLanguages"
- id="headerLanguages"
- class="languages">
- {{ t('settings', 'Language') }}
- </div>
-
- <div v-if="showConfig.showUserBackend || showConfig.showStoragePath"
- class="headerUserBackend userBackend">
- <div v-if="showConfig.showUserBackend" class="userBackend">
- {{ t('settings', 'User backend') }}
- </div>
- <div v-if="showConfig.showStoragePath"
- class="subtitle storageLocation">
- {{ t('settings', 'Storage location') }}
- </div>
- </div>
- <div v-if="showConfig.showLastLogin"
- class="headerLastLogin lastLogin">
- {{ t('settings', 'Last login') }}
- </div>
- <div id="headerManager" class="manager">
- {{ t('settings', 'Manager') }}
- </div>
- <div class="userActions" />
- </div>
-
- <UserRow v-for="user in filteredUsers"
- :key="user.id"
- :external-actions="externalActions"
- :groups="groups"
- :languages="languages"
:quota-options="quotaOptions"
- :settings="settings"
- :show-config="showConfig"
- :sub-admins-groups="subAdminsGroups"
- :user="user"
- :users="users"
- :is-dark-theme="isDarkTheme" />
-
- <InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler">
- <div slot="spinner">
- <div class="users-icon-loading icon-loading" />
- </div>
- <div slot="no-more">
- <div class="users-list-end" />
- </div>
- <div slot="no-results">
- <div id="emptycontent">
- <div class="icon-contacts-dark" />
- <h2>{{ t('settings', 'No users in here') }}</h2>
- </div>
- </div>
- </InfiniteLoading>
- </div>
+ @reset="resetForm"
+ @close="closeModal" />
+
+ <NcEmptyContent v-if="filteredUsers.length === 0"
+ class="empty"
+ :title="isInitialLoad && loading.users ? null : t('settings', 'No users')">
+ <template #icon>
+ <NcLoadingIcon v-if="isInitialLoad && loading.users"
+ :title="t('settings', 'Loading users …')"
+ :size="64" />
+ <NcIconSvgWrapper v-else
+ :svg="usersSvg" />
+ </template>
+ </NcEmptyContent>
+
+ <RecycleScroller v-else
+ class="user-list"
+ :style="style"
+ ref="scroller"
+ :items="filteredUsers"
+ key-field="id"
+ role="table"
+ list-tag="tbody"
+ list-class="user-list__body"
+ item-tag="tr"
+ item-class="user-list__row"
+ :item-size="rowHeight"
+ @hook:mounted="handleMounted"
+ @scroll-end="handleScrollEnd">
+
+ <template #before>
+ <caption class="hidden-visually">
+ {{ t('settings', 'List of users. This list is not fully rendered for performances reasons. The users will be rendered as you navigate through the list.') }}
+ </caption>
+ <UserListHeader :has-obfuscated="hasObfuscated" />
+ </template>
+
+ <template #default="{ item: user }">
+ <UserRow :user="user"
+ :users="users"
+ :settings="settings"
+ :has-obfuscated="hasObfuscated"
+ :groups="groups"
+ :sub-admins-groups="subAdminsGroups"
+ :quota-options="quotaOptions"
+ :languages="languages"
+ :external-actions="externalActions" />
+ </template>
+
+ <template #after>
+ <UserListFooter :loading="loading.users"
+ :filtered-users="filteredUsers" />
+ </template>
+
+ </RecycleScroller>
+ </Fragment>
</template>
<script>
import Vue from 'vue'
-import InfiniteLoading from 'vue-infinite-loading'
+import { Fragment } from 'vue-frag'
+import { RecycleScroller } from 'vue-virtual-scroller'
+
+import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
+import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
+import { showError } from '@nextcloud/dialogs'
-import UserRow from './Users/UserRow.vue'
import NewUserModal from './Users/NewUserModal.vue'
+import UserListFooter from './Users/UserListFooter.vue'
+import UserListHeader from './Users/UserListHeader.vue'
+import UserRow from './Users/UserRow.vue'
-const unlimitedQuota = {
- id: 'none',
- label: t('settings', 'Unlimited'),
-}
+import { defaultQuota, isObfuscated, unlimitedQuota } from '../utils/userUtils.ts'
+import logger from '../logger.js'
-const defaultQuota = {
- id: 'default',
- label: t('settings', 'Default quota'),
-}
+import usersSvg from '../../img/users.svg?raw'
const newUser = {
id: '',
@@ -155,20 +125,18 @@ export default {
name: 'UserList',
components: {
- InfiniteLoading,
+ Fragment,
+ NcEmptyContent,
+ NcIconSvgWrapper,
+ NcLoadingIcon,
NewUserModal,
+ RecycleScroller,
+ UserListFooter,
+ UserListHeader,
UserRow,
},
props: {
- users: {
- type: Array,
- default: () => [],
- },
- showConfig: {
- type: Object,
- required: true,
- },
selectedGroup: {
type: String,
default: null,
@@ -184,20 +152,39 @@ export default {
loading: {
all: false,
groups: false,
+ users: false,
},
- scrolled: false,
+ isInitialLoad: true,
+ rowHeight: 55,
+ usersSvg,
searchQuery: '',
newUser: Object.assign({}, newUser),
}
},
computed: {
+ showConfig() {
+ return this.$store.getters.getShowConfig
+ },
+
settings() {
return this.$store.getters.getServerData
},
- selectedGroupDecoded() {
- return decodeURIComponent(this.selectedGroup)
+
+ style() {
+ return {
+ '--row-height': `${this.rowHeight}px`,
+ }
+ },
+
+ hasObfuscated() {
+ return this.filteredUsers.some(user => isObfuscated(user))
},
+
+ users() {
+ return this.$store.getters.getUsers
+ },
+
filteredUsers() {
if (this.selectedGroup === 'disabled') {
return this.users.filter(user => user.enabled === false)
@@ -208,16 +195,19 @@ export default {
}
return this.users.filter(user => user.enabled !== false)
},
+
groups() {
// data provided php side + remove the disabled group
return this.$store.getters.getGroups
.filter(group => group.id !== 'disabled')
.sort((a, b) => a.name.localeCompare(b.name))
},
+
subAdminsGroups() {
// data provided php side
return this.$store.getters.getSubadminGroups
},
+
quotaOptions() {
// convert the preset array into objects
const quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({
@@ -231,12 +221,15 @@ export default {
quotaPreset.unshift(defaultQuota)
return quotaPreset
},
+
usersOffset() {
return this.$store.getters.getUsersOffset
},
+
usersLimit() {
return this.$store.getters.getUsersLimit
},
+
usersCount() {
return this.users.length
},
@@ -254,37 +247,29 @@ export default {
},
]
},
- isDarkTheme() {
- return window.getComputedStyle(this.$el)
- .getPropertyValue('--background-invert-if-dark') === 'invert(100%)'
- },
},
+
watch: {
// watch url change and group select
- selectedGroup(val, old) {
+ async selectedGroup(val, old) {
+ this.isInitialLoad = true
// if selected is the disabled group but it's empty
- this.redirectIfDisabled()
+ await this.redirectIfDisabled()
this.$store.commit('resetUsers')
- this.$refs.infiniteLoading.stateChanger.reset()
+ await this.loadUsers()
this.setNewUserDefaultGroup(val)
},
- // make sure the infiniteLoading state is changed if we manually
- // add/remove data from the store
- usersCount(val, old) {
- // deleting the last user, reset the list
- if (val === 0 && old === 1) {
- this.$refs.infiniteLoading.stateChanger.reset()
- // adding the first user, warn the infiniteLoader that
- // the list is not empty anymore (we don't fetch the newly
- // added user as we already have all the info we need)
- } else if (val === 1 && old === 0) {
- this.$refs.infiniteLoading.stateChanger.loaded()
- }
+ filteredUsers(filteredUsers) {
+ logger.debug(`${filteredUsers.length} filtered user(s)`)
},
},
- mounted() {
+ async created() {
+ await this.loadUsers()
+ },
+
+ async mounted() {
if (!this.settings.canChangePassword) {
OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled'))
}
@@ -303,40 +288,58 @@ export default {
/**
* If disabled group but empty, redirect
*/
- this.redirectIfDisabled()
+ await this.redirectIfDisabled()
},
+
beforeDestroy() {
unsubscribe('nextcloud:unified-search.search', this.search)
unsubscribe('nextcloud:unified-search.reset', this.resetSearch)
},
methods: {
- onScroll(event) {
- this.scrolled = event.target.scrollTo > 0
+ async handleMounted() {
+ // Add proper semantics to the recycle scroller slots
+ const header = this.$refs.scroller.$refs.before
+ const footer = this.$refs.scroller.$refs.after
+ header.classList.add('user-list__header')
+ header.setAttribute('role', 'rowgroup')
+ footer.classList.add('user-list__footer')
+ footer.setAttribute('role', 'rowgroup')
},
- infiniteHandler($state) {
- this.$store.dispatch('getUsers', {
- offset: this.usersOffset,
- limit: this.usersLimit,
- group: this.selectedGroup !== 'disabled' ? this.selectedGroup : '',
- search: this.searchQuery,
- })
- .then((usersCount) => {
- if (usersCount > 0) {
- $state.loaded()
- }
- if (usersCount < this.usersLimit) {
- $state.complete()
- }
+ async handleScrollEnd() {
+ await this.loadUsers()
+ },
+
+ async loadUsers() {
+ this.loading.users = true
+ try {
+ await this.$store.dispatch('getUsers', {
+ offset: this.usersOffset,
+ limit: this.usersLimit,
+ group: this.selectedGroup !== 'disabled' ? this.selectedGroup : '',
+ search: this.searchQuery,
})
+ logger.debug(`${this.users.length} total user(s) loaded`)
+ } catch (error) {
+ logger.error('Failed to load users', { error })
+ showError('Failed to load users')
+ }
+ this.loading.users = false
+ this.isInitialLoad = false
+ },
+
+ closeModal() {
+ this.$store.commit('setShowConfig', {
+ key: 'showNewUserForm',
+ value: false,
+ })
},
- /* SEARCH */
- search({ query }) {
+ async search({ query }) {
this.searchQuery = query
this.$store.commit('resetUsers')
- this.$refs.infiniteLoading.stateChanger.reset()
+ await this.loadUsers()
},
resetSearch() {
@@ -384,15 +387,86 @@ export default {
* we only check for 0 because we don't have the count on ldap
* and we therefore set the usercount to -1 in this specific case
*/
- redirectIfDisabled() {
+ async redirectIfDisabled() {
const allGroups = this.$store.getters.getGroups
if (this.selectedGroup === 'disabled'
&& allGroups.findIndex(group => group.id === 'disabled' && group.usercount === 0) > -1) {
// disabled group is empty, redirection to all users
this.$router.push({ name: 'users' })
- this.$refs.infiniteLoading.stateChanger.reset()
+ await this.loadUsers()
}
},
},
}
</script>
+
+<style lang="scss" scoped>
+@import './Users/shared/styles.scss';
+
+.empty {
+ :deep {
+ .icon-vue {
+ width: 64px;
+ height: 64px;
+
+ svg {
+ max-width: 64px;
+ max-height: 64px;
+ }
+ }
+ }
+}
+
+.user-list {
+ --avatar-cell-width: 48px;
+ --cell-padding: 7px;
+ --cell-width: 200px;
+ --cell-min-width: calc(var(--cell-width) - (2 * var(--cell-padding)));
+
+ display: block;
+ overflow: auto;
+ height: 100%;
+
+ :deep {
+ .user-list {
+ &__body {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ // Necessary for virtual scrolling absolute
+ position: relative;
+ margin-top: var(--row-height);
+ }
+
+ &__row {
+ @include row;
+ border-bottom: 1px solid var(--color-border);
+
+ &:hover {
+ background-color: var(--color-background-hover);
+
+ .row__cell:not(.row__cell--actions) {
+ background-color: var(--color-background-hover);
+ }
+ }
+ }
+ }
+
+ .vue-recycle-scroller__slot {
+ &.user-list__header,
+ &.user-list__footer {
+ position: sticky;
+ }
+
+ &.user-list__header {
+ top: 0;
+ z-index: 10;
+ }
+
+ &.user-list__footer {
+ left: 0;
+ }
+ }
+ }
+}
+</style>
diff --git a/apps/settings/src/components/Users/NewUserModal.vue b/apps/settings/src/components/Users/NewUserModal.vue
index be7714f9373..1ad4a6fafaa 100644
--- a/apps/settings/src/components/Users/NewUserModal.vue
+++ b/apps/settings/src/components/Users/NewUserModal.vue
@@ -182,16 +182,6 @@ import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
-const unlimitedQuota = {
- id: 'none',
- label: t('settings', 'Unlimited'),
-}
-
-const defaultQuota = {
- id: 'default',
- label: t('settings', 'Default quota'),
-}
-
export default {
name: 'NewUserModal',
@@ -214,8 +204,8 @@ export default {
required: true,
},
- showConfig: {
- type: Object,
+ quotaOptions: {
+ type: Array,
required: true,
},
},
@@ -227,6 +217,10 @@ export default {
},
computed: {
+ showConfig() {
+ return this.$store.getters.getShowConfig
+ },
+
settings() {
return this.$store.getters.getServerData
},
@@ -265,20 +259,6 @@ export default {
})
},
- quotaOptions() {
- // convert the preset array into objects
- const quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({
- id: cur,
- label: cur,
- }), [])
- // add default presets
- if (this.settings.allowUnlimitedQuota) {
- quotaPreset.unshift(unlimitedQuota)
- }
- quotaPreset.unshift(defaultQuota)
- return quotaPreset
- },
-
languages() {
return [
{
diff --git a/apps/settings/src/components/Users/UserListFooter.vue b/apps/settings/src/components/Users/UserListFooter.vue
new file mode 100644
index 00000000000..c8713369203
--- /dev/null
+++ b/apps/settings/src/components/Users/UserListFooter.vue
@@ -0,0 +1,126 @@
+<!--
+ - @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ -
+ - @author Christopher Ng <chrng8@gmail.com>
+ -
+ - @license AGPL-3.0-or-later
+ -
+ - 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/>.
+ -
+-->
+
+<template>
+ <tr class="footer">
+ <th scope="row">
+ <span class="hidden-visually">{{ t('settings', 'Total rows summary') }}</span>
+ </th>
+ <td class="footer__cell footer__cell--loading">
+ <NcLoadingIcon v-if="loading"
+ :title="t('settings', 'Loading users …')"
+ :size="32" />
+ </td>
+ <td class="footer__cell footer__cell--count footer__cell--multiline">
+ <span aria-describedby="user-count-desc">{{ userCount }}</span>
+ <span id="user-count-desc"
+ class="hidden-visually">
+ {{ t('settings', 'Scroll to load more rows') }}
+ </span>
+ </td>
+ </tr>
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+
+import {
+ translate as t,
+ translatePlural as n,
+} from '@nextcloud/l10n'
+
+export default Vue.extend({
+ name: 'UserListFooter',
+
+ components: {
+ NcLoadingIcon,
+ },
+
+ props: {
+ loading: {
+ type: Boolean,
+ required: true,
+ },
+ filteredUsers: {
+ type: Array,
+ required: true,
+ },
+ },
+
+ computed: {
+ userCount(): string {
+ if (this.loading) {
+ return this.n(
+ 'settings',
+ '{userCount} user …',
+ '{userCount} users …',
+ this.filteredUsers.length,
+ {
+ userCount: this.filteredUsers.length,
+ },
+ )
+ }
+ return this.n(
+ 'settings',
+ '{userCount} user',
+ '{userCount} users',
+ this.filteredUsers.length,
+ {
+ userCount: this.filteredUsers.length,
+ },
+ )
+ },
+ },
+
+ methods: {
+ t,
+ n,
+ },
+})
+</script>
+
+<style lang="scss" scoped>
+@import './shared/styles.scss';
+
+.footer {
+ @include row;
+ @include cell;
+
+ &__cell {
+ position: sticky;
+ color: var(--color-text-maxcontrast);
+
+ &--loading {
+ left: 0;
+ width: var(--avatar-cell-width);
+ align-items: center;
+ padding: 0;
+ }
+
+ &--count {
+ left: var(--avatar-cell-width);
+ width: var(--cell-width);
+ }
+ }
+}
+</style>
diff --git a/apps/settings/src/components/Users/UserListHeader.vue b/apps/settings/src/components/Users/UserListHeader.vue
new file mode 100644
index 00000000000..3357d1c1bc2
--- /dev/null
+++ b/apps/settings/src/components/Users/UserListHeader.vue
@@ -0,0 +1,150 @@
+<!--
+ - @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ -
+ - @author Christopher Ng <chrng8@gmail.com>
+ -
+ - @license AGPL-3.0-or-later
+ -
+ - 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/>.
+ -
+-->
+
+<template>
+ <tr class="header">
+ <th class="header__cell header__cell--avatar"
+ scope="col">
+ <span class="hidden-visually">
+ {{ t('settings', 'Avatar') }}
+ </span>
+ </th>
+ <th class="header__cell header__cell--displayname"
+ scope="col">
+ <strong>
+ {{ t('settings', 'Display name') }}
+ </strong>
+ <span class="header__subtitle">
+ {{ t('settings', 'Username') }}
+ </span>
+ </th>
+ <th class="header__cell"
+ :class="{ 'header__cell--obfuscated': hasObfuscated }"
+ scope="col">
+ <span>{{ passwordLabel }}</span>
+ </th>
+ <th class="header__cell"
+ scope="col">
+ <span>{{ t('settings', 'Email') }}</span>
+ </th>
+ <th class="header__cell header__cell--large"
+ scope="col">
+ <span>{{ t('settings', 'Groups') }}</span>
+ </th>
+ <th v-if="subAdminsGroups.length > 0 && settings.isAdmin"
+ class="header__cell header__cell--large"
+ scope="col">
+ <span>{{ t('settings', 'Group admin for') }}</span>
+ </th>
+ <th class="header__cell"
+ scope="col">
+ <span>{{ t('settings', 'Quota') }}</span>
+ </th>
+ <th v-if="showConfig.showLanguages"
+ class="header__cell header__cell--large"
+ scope="col">
+ <span>{{ t('settings', 'Language') }}</span>
+ </th>
+ <th v-if="showConfig.showUserBackend || showConfig.showStoragePath"
+ class="header__cell header__cell--large"
+ scope="col">
+ <span v-if="showConfig.showUserBackend">
+ {{ t('settings', 'User backend') }}
+ </span>
+ <span v-if="showConfig.showStoragePath"
+ class="header__subtitle">
+ {{ t('settings', 'Storage location') }}
+ </span>
+ </th>
+ <th v-if="showConfig.showLastLogin"
+ class="header__cell"
+ scope="col">
+ <span>{{ t('settings', 'Last login') }}</span>
+ </th>
+ <th class="header__cell header__cell--large"
+ scope="col">
+ <span>{{ t('settings', 'Manager') }}</span>
+ </th>
+ <th class="header__cell header__cell--actions"
+ scope="col">
+ <span class="hidden-visually">
+ {{ t('settings', 'User actions') }}
+ </span>
+ </th>
+ </tr>
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+
+import { translate as t } from '@nextcloud/l10n'
+
+export default Vue.extend({
+ name: 'UserListHeader',
+
+ props: {
+ hasObfuscated: {
+ type: Boolean,
+ required: true,
+ },
+ },
+
+ computed: {
+ showConfig() {
+ // @ts-expect-error: allow untyped $store
+ return this.$store.getters.getShowConfig
+ },
+
+ settings() {
+ // @ts-expect-error: allow untyped $store
+ return this.$store.getters.getServerData
+ },
+
+ subAdminsGroups() {
+ // @ts-expect-error: allow untyped $store
+ return this.$store.getters.getSubadminGroups
+ },
+
+ passwordLabel(): string {
+ if (this.hasObfuscated) {
+ return t('settings', 'Password or insufficient permissions message')
+ }
+ return t('settings', 'Password')
+ },
+ },
+
+ methods: {
+ t,
+ },
+})
+</script>
+
+<style lang="scss" scoped>
+@import './shared/styles.scss';
+
+.header {
+ @include row;
+ @include cell;
+
+ border-bottom: 1px solid var(--color-border);
+}
+</style>
diff --git a/apps/settings/src/components/Users/UserRow.vue b/apps/settings/src/components/Users/UserRow.vue
index 6d5850068de..83d1fcd8160 100644
--- a/apps/settings/src/components/Users/UserRow.vue
+++ b/apps/settings/src/components/Users/UserRow.vue
@@ -1,9 +1,10 @@
<!--
- - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
- @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
+ - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
-
- - @author John Molakvoæ <skjnldsv@protonmail.com>
+ - @author Christopher Ng <chrng8@gmail.com>
- @author Gary Kim <gary@garykim.dev>
+ - @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
@@ -23,289 +24,356 @@
-->
<template>
- <!-- Obfuscated user: Logged in user does not have permissions to see all of the data -->
- <div v-if="Object.keys(user).length ===1" :data-id="user.id" class="row">
- <div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"
- class="avatar">
- <img v-if="!loading.delete && !loading.disable && !loading.wipe"
- :src="generateAvatar(user.id, isDarkTheme)"
- alt=""
- height="32"
- width="32">
- </div>
- <div class="name">
- {{ user.id }}
- </div>
- <div class="obfuscated">
- {{ t('settings','You do not have permissions to see the details of this user') }}
- </div>
- </div>
-
- <!-- User full data -->
- <UserRowSimple v-else-if="!editing"
- :editing.sync="editing"
- :groups="groups"
- :languages="languages"
- :loading="loading"
- :opened-menu.sync="openedMenu"
- :settings="settings"
- :show-config="showConfig"
- :sub-admins-groups="subAdminsGroups"
- :user-actions="userActions"
- :user="user"
- :is-dark-theme="isDarkTheme"
- :class="{'row--menu-opened': openedMenu}" />
- <div v-else
- :class="{
- 'disabled': loading.delete || loading.disable,
- 'row--menu-opened': openedMenu
- }"
- :data-id="user.id"
- class="row row--editable">
- <div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"
- class="avatar">
- <img v-if="!loading.delete && !loading.disable && !loading.wipe"
- :src="generateAvatar(user.id, isDarkTheme)"
- alt=""
- height="32"
- width="32">
- </div>
- <!-- dirty hack to ellipsis on two lines -->
- <div v-if="user.backendCapabilities.setDisplayName" class="displayName">
- <label class="hidden-visually" :for="'displayName'+user.id+rand">{{ t('settings', 'Edit display name') }}</label>
- <NcTextField :id="'displayName'+user.id+rand"
- :show-trailing-button="true"
- class="user-row-text-field"
- :class="{'icon-loading-small': loading.displayName}"
- :disabled="loading.displayName||loading.all"
- trailing-button-icon="arrowRight"
- :value.sync="editedDisplayName"
- autocapitalize="off"
- autocomplete="off"
- autocorrect="off"
- spellcheck="false"
- type="text"
- @trailing-button-click="updateDisplayName" />
- </div>
- <div v-else class="name">
- {{ user.id }}
- <div class="displayName subtitle">
- <div :title="user.displayname.length > 20 ? user.displayname : ''" class="cellText">
+ <Fragment>
+ <td class="row__cell row__cell--avatar">
+ <NcLoadingIcon v-if="isLoadingUser"
+ :title="t('settings', 'Loading user …')"
+ :size="32" />
+ <NcAvatar v-else
+ :key="user.id"
+ disable-menu
+ :show-user-status="false"
+ :user="user.id" />
+ </td>
+
+ <td class="row__cell row__cell--displayname"
+ :data-test="user.id">
+ <template v-if="idState.editing && user.backendCapabilities.setDisplayName">
+ <label class="hidden-visually"
+ :for="'displayName' + uniqueId">
+ {{ t('settings', 'Edit display name') }}
+ </label>
+ <NcTextField :id="'displayName' + uniqueId"
+ data-test="displayNameField"
+ ref="displayNameField"
+ :show-trailing-button="true"
+ class="user-row-text-field"
+ :class="{ 'icon-loading-small': idState.loading.displayName }"
+ :disabled="idState.loading.displayName || isLoadingField"
+ trailing-button-icon="arrowRight"
+ :value.sync="idState.editedDisplayName"
+ autocapitalize="off"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ type="text"
+ @trailing-button-click="updateDisplayName" />
+ </template>
+ <template v-else>
+ <strong v-if="!isObfuscated"
+ :title="user.displayname?.length > 20 ? user.displayname : null">
{{ user.displayname }}
- </div>
- </div>
- </div>
- <div v-if="settings.canChangePassword && user.backendCapabilities.setPassword" class="password">
- <label class="hidden-visually" :for="'password'+user.id+rand">{{ t('settings', 'Add new password') }}</label>
- <NcTextField :id="'password'+user.id+rand"
- :show-trailing-button="true"
- class="user-row-text-field"
- :class="{'icon-loading-small': loading.password}"
- :disabled="loading.password || loading.all"
- :minlength="minPasswordLength"
- maxlength="469"
- :placeholder="t('settings', 'Add new password')"
- trailing-button-icon="arrowRight"
- :value.sync="editedPassword"
- autocapitalize="off"
- autocomplete="new-password"
- autocorrect="off"
- required
- spellcheck="false"
- type="password"
- @trailing-button-click="updatePassword" />
- </div>
-
- <div v-else />
-
- <div class="mailAddress">
- <label class="hidden-visually" :for="'mailAddress'+user.id+rand">{{ t('settings', 'Add new email address') }}</label>
- <NcTextField :id="'mailAddress'+user.id+rand"
- :show-trailing-button="true"
- class="user-row-text-field"
- :class="{'icon-loading-small': loading.mailAddress}"
- :disabled="loading.mailAddress||loading.all"
- :placeholder="t('settings', 'Add new email address')"
- trailing-button-icon="arrowRight"
- :value.sync="editedMail"
- autocapitalize="off"
- autocomplete="new-password"
- autocorrect="off"
- spellcheck="false"
- type="email"
- @trailing-button-click="updateEmail" />
- </div>
- <div :class="{'icon-loading-small': loading.groups}" class="groups">
- <label class="hidden-visually" :for="'groups'+user.id+rand">{{ t('settings', 'Add user to group') }}</label>
- <NcSelect :input-id="'groups'+user.id+rand"
- :close-on-select="false"
- :disabled="loading.groups||loading.all"
- :multiple="true"
- :options="availableGroups"
- :placeholder="t('settings', 'Add user to group')"
- :taggable="settings.isAdmin"
- :value="userGroups"
- class="select-vue"
- label="name"
- :no-wrap="true"
- :selectable="() => userGroups.length < 2"
- :create-option="(value) => ({ name: value, isCreating: true })"
- @option:created="createGroup"
- @option:selected="options => addUserGroup(options.at(-1))"
- @option:deselected="removeUserGroup" />
- </div>
- <div v-if="subAdminsGroups.length>0 && settings.isAdmin"
- :class="{'icon-loading-small': loading.subadmins}"
- class="subadmins">
- <label class="hidden-visually" :for="'subadmins'+user.id+rand">{{ t('settings', 'Set user as admin for') }}</label>
- <NcSelect :id="'subadmins'+user.id+rand"
- :close-on-select="false"
- :disabled="loading.subadmins||loading.all"
- label="name"
- :multiple="true"
- :no-wrap="true"
- :selectable="() => userSubAdminsGroups.length < 2"
- :options="subAdminsGroups"
- :placeholder="t('settings', 'Set user as admin for')"
- :value="userSubAdminsGroups"
- class="select-vue"
- @option:deselected="removeUserSubAdmin"
- @option:selected="options => addUserSubAdmin(options.at(-1))" />
- </div>
- <div :title="usedSpace"
- :class="{'icon-loading-small': loading.quota}"
- class="quota">
- <label class="hidden-visually" :for="'quota'+user.id+rand">{{ t('settings', 'Select user quota') }}</label>
- <NcSelect v-model="userQuota"
- :close-on-select="true"
- :create-option="validateQuota"
- :disabled="loading.quota||loading.all"
- :input-id="'quota'+user.id+rand"
- class="select-vue"
- :options="quotaOptions"
- :placeholder="t('settings', 'Select user quota')"
- :taggable="true"
- @option:selected="setUserQuota" />
- </div>
- <div v-if="showConfig.showLanguages"
- :class="{'icon-loading-small': loading.languages}"
- class="languages">
- <label class="hidden-visually" :for="'language'+user.id+rand">{{ t('settings', 'Set the language') }}</label>
- <NcSelect :id="'language'+user.id+rand"
- :allow-empty="false"
- :disabled="loading.languages||loading.all"
- :options="availableLanguages"
- :placeholder="t('settings', 'No language set')"
- :value="userLanguage"
- label="name"
- class="select-vue"
- @input="setUserLanguage" />
- </div>
-
- <div v-if="showConfig.showStoragePath || showConfig.showUserBackend"
- class="storageLocation" />
- <div v-if="showConfig.showLastLogin" />
-
- <div :class="{'icon-loading-small': loading.manager}" class="managers">
- <label class="hidden-visually" :for="'manager'+user.id+rand">{{ t('settings', 'Set the language') }}</label>
- <NcSelect v-model="currentManager"
- :input-id="'manager'+user.id+rand"
- :close-on-select="true"
- label="displayname"
- :options="possibleManagers"
- :placeholder="t('settings', 'Select manager')"
- class="select-vue"
- @search="searchUserManager"
- @option:selected="updateUserManager"
- @input="updateUserManager" />
- </div>
-
- <div class="userActions">
- <UserRowActions v-if="!loading.all"
+ </strong>
+ <span class="row__subtitle">{{ user.id }}</span>
+ </template>
+ </td>
+
+ <td class="row__cell"
+ :class="{ 'row__cell--obfuscated': hasObfuscated }">
+ <template v-if="idState.editing && settings.canChangePassword && user.backendCapabilities.setPassword">
+ <label class="hidden-visually"
+ :for="'password' + uniqueId">
+ {{ t('settings', 'Add new password') }}
+ </label>
+ <NcTextField :id="'password' + uniqueId"
+ :show-trailing-button="true"
+ class="user-row-text-field"
+ :class="{'icon-loading-small': idState.loading.password}"
+ :disabled="idState.loading.password || isLoadingField"
+ :minlength="minPasswordLength"
+ maxlength="469"
+ :placeholder="t('settings', 'Add new password')"
+ trailing-button-icon="arrowRight"
+ :value.sync="idState.editedPassword"
+ autocapitalize="off"
+ autocomplete="new-password"
+ autocorrect="off"
+ required
+ spellcheck="false"
+ type="password"
+ @trailing-button-click="updatePassword" />
+ </template>
+ <span v-else-if="isObfuscated">
+ {{ t('settings', 'You do not have permissions to see the details of this user') }}
+ </span>
+ </td>
+
+ <td class="row__cell">
+ <template v-if="idState.editing">
+ <label class="hidden-visually"
+ :for="'mailAddress' + uniqueId">
+ {{ t('settings', 'Add new email address') }}
+ </label>
+ <NcTextField :id="'mailAddress' + uniqueId"
+ :show-trailing-button="true"
+ class="user-row-text-field"
+ :class="{'icon-loading-small': idState.loading.mailAddress}"
+ :disabled="idState.loading.mailAddress || isLoadingField"
+ :placeholder="t('settings', 'Add new email address')"
+ trailing-button-icon="arrowRight"
+ :value.sync="idState.editedMail"
+ autocapitalize="off"
+ autocomplete="new-password"
+ autocorrect="off"
+ spellcheck="false"
+ type="email"
+ @trailing-button-click="updateEmail" />
+ </template>
+ <span v-else-if="!isObfuscated"
+ :title="user.email?.length > 20 ? user.email : null">
+ {{ user.email }}
+ </span>
+ </td>
+
+ <td class="row__cell row__cell--large row__cell--multiline">
+ <template v-if="idState.editing">
+ <label class="hidden-visually"
+ :for="'groups' + uniqueId">
+ {{ t('settings', 'Add user to group') }}
+ </label>
+ <NcSelect :input-id="'groups' + uniqueId"
+ :close-on-select="false"
+ :disabled="idState.loading.groups || isLoadingField"
+ :loading="idState.loading.groups"
+ :multiple="true"
+ :options="availableGroups"
+ :placeholder="t('settings', 'Add user to group')"
+ :taggable="settings.isAdmin"
+ :value="userGroups"
+ class="select-vue"
+ label="name"
+ :no-wrap="true"
+ :create-option="(value) => ({ name: value, isCreating: true })"
+ @option:created="createGroup"
+ @option:selected="options => addUserGroup(options.at(-1))"
+ @option:deselected="removeUserGroup" />
+ </template>
+ <span v-else-if="!isObfuscated"
+ :title="userGroupsLabels?.length > 40 ? userGroupsLabels : null">
+ {{ userGroupsLabels }}
+ </span>
+ </td>
+
+ <td v-if="subAdminsGroups.length > 0 && settings.isAdmin"
+ class="row__cell row__cell--large row__cell--multiline">
+ <template v-if="idState.editing && settings.isAdmin && subAdminsGroups.length > 0">
+ <label class="hidden-visually"
+ :for="'subadmins' + uniqueId">
+ {{ t('settings', 'Set user as admin for') }}
+ </label>
+ <NcSelect :id="'subadmins' + uniqueId"
+ :close-on-select="false"
+ :disabled="idState.loading.subadmins || isLoadingField"
+ :loading="idState.loading.subadmins"
+ label="name"
+ :multiple="true"
+ :no-wrap="true"
+ :options="subAdminsGroups"
+ :placeholder="t('settings', 'Set user as admin for')"
+ :value="userSubAdminsGroups"
+ class="select-vue"
+ @option:deselected="removeUserSubAdmin"
+ @option:selected="options => addUserSubAdmin(options.at(-1))" />
+ </template>
+ <span v-else-if="!isObfuscated"
+ :title="userSubAdminsGroupsLabels?.length > 40 ? userSubAdminsGroupsLabels : null">
+ {{ userSubAdminsGroupsLabels }}
+ </span>
+ </td>
+
+ <td class="row__cell">
+ <template v-if="idState.editing">
+ <label class="hidden-visually"
+ :for="'quota' + uniqueId">
+ {{ t('settings', 'Select user quota') }}
+ </label>
+ <NcSelect v-model="editedUserQuota"
+ :close-on-select="true"
+ :create-option="validateQuota"
+ :disabled="idState.loading.quota || isLoadingField"
+ :loading="idState.loading.quota"
+ :clearable="false"
+ :input-id="'quota' + uniqueId"
+ class="select-vue"
+ :options="quotaOptions"
+ :placeholder="t('settings', 'Select user quota')"
+ :taggable="true"
+ @option:selected="setUserQuota" />
+ </template>
+ <template v-else-if="!isObfuscated">
+ <label :for="'quota-progress' + uniqueId">{{ userQuota }} ({{ usedSpace }})</label>
+ <NcProgressBar class="row__progress"
+ :id="'quota-progress' + uniqueId"
+ :class="{
+ 'row__progress--warn': usedQuota > 80,
+ }"
+ :value="usedQuota" />
+ </template>
+ </td>
+
+ <td v-if="showConfig.showLanguages"
+ class="row__cell row__cell--large"
+ data-test="language">
+ <template v-if="idState.editing">
+ <label class="hidden-visually"
+ :for="'language' + uniqueId">
+ {{ t('settings', 'Set the language') }}
+ </label>
+ <NcSelect :id="'language' + uniqueId"
+ :allow-empty="false"
+ :disabled="idState.loading.languages || isLoadingField"
+ :loading="idState.loading.languages"
+ :clearable="false"
+ :options="availableLanguages"
+ :placeholder="t('settings', 'No language set')"
+ :value="userLanguage"
+ label="name"
+ class="select-vue"
+ @input="setUserLanguage" />
+ </template>
+ <span v-else-if="!isObfuscated">
+ {{ userLanguage.name }}
+ </span>
+ </td>
+
+ <td v-if="showConfig.showUserBackend || showConfig.showStoragePath"
+ class="row__cell row__cell--large">
+ <template v-if="!isObfuscated">
+ <span v-if="showConfig.showUserBackend">{{ user.backend }}</span>
+ <span v-if="showConfig.showStoragePath"
+ :title="user.storageLocation"
+ class="row__subtitle">
+ {{ user.storageLocation }}
+ </span>
+ </template>
+ </td>
+
+ <td v-if="showConfig.showLastLogin"
+ :title="userLastLoginTooltip"
+ class="row__cell"
+ data-test="lastLogin">
+ <span v-if="!isObfuscated">{{ userLastLogin }}</span>
+ </td>
+
+ <td class="row__cell row__cell--large">
+ <template v-if="idState.editing">
+ <label class="hidden-visually"
+ :for="'manager' + uniqueId">
+ {{ t('settings', 'Set the manager') }}
+ </label>
+ <NcSelect v-model="idState.currentManager"
+ :input-id="'manager' + uniqueId"
+ :close-on-select="true"
+ :disabled="idState.loading.manager || isLoadingField"
+ :loading="idState.loading.manager"
+ label="displayname"
+ :options="idState.possibleManagers"
+ :placeholder="t('settings', 'Select manager')"
+ class="select-vue"
+ @search="searchUserManager"
+ @option:selected="updateUserManager"
+ @input="updateUserManager" />
+ </template>
+ <span v-else-if="!isObfuscated">
+ {{ user.manager }}
+ </span>
+ </td>
+
+ <td class="row__cell row__cell--actions">
+ <UserRowActions v-if="!isObfuscated && canEdit && !idState.loading.all"
:actions="userActions"
- :edit="true"
+ :disabled="isLoadingField"
+ :edit="idState.editing"
@update:edit="toggleEdit" />
- </div>
- </div>
+ </td>
+ </Fragment>
</template>
<script>
+import { Fragment } from 'vue-frag'
+import { IdState } from 'vue-virtual-scroller'
+import { getCurrentUser } from '@nextcloud/auth'
import { showSuccess, showError } from '@nextcloud/dialogs'
+import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
-import ClickOutside from 'vue-click-outside'
import UserRowActions from './UserRowActions.vue'
-import UserRowSimple from './UserRowSimple.vue'
+
import UserRowMixin from '../../mixins/UserRowMixin.js'
+import { isObfuscated, unlimitedQuota } from '../../utils/userUtils.ts'
export default {
name: 'UserRow',
components: {
+ Fragment,
+ NcAvatar,
+ NcLoadingIcon,
+ NcProgressBar,
NcSelect,
NcTextField,
UserRowActions,
- UserRowSimple,
},
- directives: {
- ClickOutside,
- },
-
- mixins: [UserRowMixin],
+ mixins: [
+ /**
+ * Use scoped `idState` instead of `data` which is reused between rows
+ *
+ * See https://github.com/Akryum/vue-virtual-scroller/tree/v1/packages/vue-virtual-scroller#why-is-this-useful
+ */
+ IdState({
+ idProp: vm => vm.user.id,
+ }),
+ UserRowMixin,
+ ],
props: {
+ user: {
+ type: Object,
+ required: true,
+ },
users: {
type: Array,
required: true,
},
- user: {
- type: Object,
+ hasObfuscated: {
+ type: Boolean,
required: true,
},
- settings: {
- type: Object,
- default: () => ({}),
- },
groups: {
type: Array,
default: () => [],
},
subAdminsGroups: {
type: Array,
- default: () => [],
+ required: true,
},
quotaOptions: {
type: Array,
- default: () => [],
- },
- showConfig: {
- type: Object,
- default: () => ({}),
+ required: true,
},
languages: {
type: Array,
required: true,
},
+ settings: {
+ type: Object,
+ required: true,
+ },
externalActions: {
type: Array,
default: () => [],
},
- isDarkTheme: {
- type: Boolean,
- required: true,
- },
},
- data() {
+
+ idState() {
return {
- // default quota is set to unlimited
- unlimitedQuota: { id: 'none', label: t('settings', 'Unlimited') },
- // temporary value used for multiselect change
selectedQuota: false,
- rand: parseInt(Math.random() * 1000),
- openedMenu: false,
+ rand: Math.random().toString(36).substring(2),
possibleManagers: [],
currentManager: '',
editing: false,
@@ -330,7 +398,69 @@ export default {
},
computed: {
- /* USER POPOVERMENU ACTIONS */
+ isObfuscated() {
+ return isObfuscated(this.user)
+ },
+
+ showConfig() {
+ return this.$store.getters.getShowConfig
+ },
+
+ isLoadingUser() {
+ return this.idState.loading.delete || this.idState.loading.disable || this.idState.loading.wipe
+ },
+
+ isLoadingField() {
+ return this.idState.loading.delete || this.idState.loading.disable || this.idState.loading.all
+ },
+
+ uniqueId() {
+ return this.user.id + this.idState.rand
+ },
+
+ userGroupsLabels() {
+ return this.userGroups
+ .map(group => group.name)
+ .join(', ')
+ },
+
+ userSubAdminsGroupsLabels() {
+ return this.userSubAdminsGroups
+ .map(group => group.name)
+ .join(', ')
+ },
+
+ usedSpace() {
+ if (this.user.quota?.used) {
+ return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota?.used) })
+ }
+ return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
+ },
+
+ canEdit() {
+ return getCurrentUser().uid !== this.user.id || this.settings.isAdmin
+ },
+
+ userQuota() {
+ let quota = this.user.quota?.quota
+
+ if (quota === 'default') {
+ quota = this.settings.defaultQuota
+ if (quota !== 'none') {
+ // convert to numeric value to match what the server would usually return
+ quota = OC.Util.computerFileSize(quota)
+ }
+ }
+
+ // when the default quota is unlimited, the server returns -3 here, map it to "none"
+ if (quota === 'none' || quota === -3) {
+ return t('settings', 'Unlimited')
+ } else if (quota >= 0) {
+ return OC.Util.humanFileSize(quota)
+ }
+ return OC.Util.humanFileSize(0)
+ },
+
userActions() {
const actions = [
{
@@ -360,19 +490,19 @@ export default {
},
// mapping saved values to objects
- userQuota: {
+ editedUserQuota: {
get() {
- if (this.selectedQuota !== false) {
- return this.selectedQuota
+ if (this.idState.selectedQuota !== false) {
+ return this.idState.selectedQuota
}
- if (this.settings.defaultQuota !== this.unlimitedQuota.id && OC.Util.computerFileSize(this.settings.defaultQuota) >= 0) {
+ if (this.settings.defaultQuota !== unlimitedQuota.id && OC.Util.computerFileSize(this.settings.defaultQuota) >= 0) {
// if value is valid, let's map the quotaOptions or return custom quota
return { id: this.settings.defaultQuota, label: this.settings.defaultQuota }
}
- return this.unlimitedQuota // unlimited
+ return unlimitedQuota // unlimited
},
set(quota) {
- this.selectedQuota = quota
+ this.idState.selectedQuota = quota
},
},
@@ -390,14 +520,6 @@ export default {
},
methods: {
- /* MENU HANDLING */
- toggleMenu() {
- this.openedMenu = !this.openedMenu
- },
- hideMenu() {
- this.openedMenu = false
- },
-
wipeUserDevices() {
const userid = this.user.id
OC.dialogs.confirmDestructive(
@@ -411,13 +533,13 @@ export default {
},
(result) => {
if (result) {
- this.loading.wipe = true
- this.loading.all = true
+ this.idState.loading.wipe = true
+ this.idState.loading.all = true
this.$store.dispatch('wipeUserDevices', userid)
.then(() => showSuccess(t('settings', 'Wiped {userid}\'s devices', { userid })), { timeout: 2000 })
.finally(() => {
- this.loading.wipe = false
- this.loading.all = false
+ this.idState.loading.wipe = false
+ this.idState.loading.all = false
})
}
},
@@ -428,36 +550,38 @@ export default {
filterManagers(managers) {
return managers.filter((manager) => manager.id !== this.user.id)
},
+
async initManager(userId) {
await this.$store.dispatch('getUser', userId).then(response => {
- this.currentManager = response?.data.ocs.data
+ this.idState.currentManager = response?.data.ocs.data
})
},
+
async searchUserManager(query) {
await this.$store.dispatch('searchUsers', { offset: 0, limit: 10, search: query }).then(response => {
const users = response?.data ? this.filterManagers(Object.values(response?.data.ocs.data.users)) : []
if (users.length > 0) {
- this.possibleManagers = users
+ this.idState.possibleManagers = users
}
})
},
updateUserManager(manager) {
if (manager === null) {
- this.currentManager = ''
+ this.idState.currentManager = ''
}
- this.loading.manager = true
+ this.idState.loading.manager = true
try {
this.$store.dispatch('setUserData', {
userid: this.user.id,
key: 'manager',
- value: this.currentManager ? this.currentManager.id : '',
+ value: this.idState.currentManager ? this.idState.currentManager.id : '',
})
} catch (error) {
showError(t('setting', 'Update of user manager was failed'))
console.error(error)
} finally {
- this.loading.manager = false
+ this.idState.loading.manager = false
}
},
@@ -474,12 +598,12 @@ export default {
},
(result) => {
if (result) {
- this.loading.delete = true
- this.loading.all = true
+ this.idState.loading.delete = true
+ this.idState.loading.all = true
return this.$store.dispatch('deleteUser', userid)
.then(() => {
- this.loading.delete = false
- this.loading.all = false
+ this.idState.loading.delete = false
+ this.idState.loading.all = false
})
}
},
@@ -488,8 +612,8 @@ export default {
},
enableDisableUser() {
- this.loading.delete = true
- this.loading.all = true
+ this.idState.loading.delete = true
+ this.idState.loading.all = true
const userid = this.user.id
const enabled = !this.user.enabled
return this.$store.dispatch('enableDisableUser', {
@@ -497,8 +621,8 @@ export default {
enabled,
})
.then(() => {
- this.loading.delete = false
- this.loading.all = false
+ this.idState.loading.delete = false
+ this.idState.loading.all = false
})
},
@@ -508,14 +632,14 @@ export default {
* @param {string} displayName The display name
*/
updateDisplayName() {
- this.loading.displayName = true
+ this.idState.loading.displayName = true
this.$store.dispatch('setUserData', {
userid: this.user.id,
key: 'displayname',
- value: this.editedDisplayName,
+ value: this.idState.editedDisplayName,
}).then(() => {
- this.loading.displayName = false
- if (this.editedDisplayName === this.user.displayname) {
+ this.idState.loading.displayName = false
+ if (this.idState.editedDisplayName === this.user.displayname) {
showSuccess(t('setting', 'Display name was successfully changed'))
}
})
@@ -527,18 +651,18 @@ export default {
* @param {string} password The email address
*/
updatePassword() {
- this.loading.password = true
- if (this.editedPassword.length === 0) {
+ this.idState.loading.password = true
+ if (this.idState.editedPassword.length === 0) {
showError(t('setting', "Password can't be empty"))
- this.loading.password = false
+ this.idState.loading.password = false
} else {
this.$store.dispatch('setUserData', {
userid: this.user.id,
key: 'password',
- value: this.editedPassword,
+ value: this.idState.editedPassword,
}).then(() => {
- this.loading.password = false
- this.editedPassword = ''
+ this.idState.loading.password = false
+ this.idState.editedPassword = ''
showSuccess(t('setting', 'Password was successfully changed'))
})
}
@@ -550,19 +674,19 @@ export default {
* @param {string} mailAddress The email address
*/
updateEmail() {
- this.loading.mailAddress = true
- if (this.editedMail === '') {
+ this.idState.loading.mailAddress = true
+ if (this.idState.editedMail === '') {
showError(t('setting', "Email can't be empty"))
- this.loading.mailAddress = false
- this.editedMail = this.user.email
+ this.idState.loading.mailAddress = false
+ this.idState.editedMail = this.user.email
} else {
this.$store.dispatch('setUserData', {
userid: this.user.id,
key: 'email',
- value: this.editedMail,
+ value: this.idState.editedMail,
}).then(() => {
- this.loading.mailAddress = false
- if (this.editedMail === this.user.email) {
+ this.idState.loading.mailAddress = false
+ if (this.idState.editedMail === this.user.email) {
showSuccess(t('setting', 'Email was successfully changed'))
}
})
@@ -575,7 +699,7 @@ export default {
* @param {string} gid Group id
*/
async createGroup({ name: gid }) {
- this.loading = { groups: true, subadmins: true }
+ this.idState.loading = { groups: true, subadmins: true }
try {
await this.$store.dispatch('addGroup', gid)
const userid = this.user.id
@@ -583,7 +707,7 @@ export default {
} catch (error) {
console.error(error)
} finally {
- this.loading = { groups: false, subadmins: false }
+ this.idState.loading = { groups: false, subadmins: false }
}
return this.$store.getters.getGroups[this.groups.length]
},
@@ -599,7 +723,7 @@ export default {
// Ignore
return
}
- this.loading.groups = true
+ this.idState.loading.groups = true
const userid = this.user.id
const gid = group.id
if (group.canAdd === false) {
@@ -610,7 +734,7 @@ export default {
} catch (error) {
console.error(error)
} finally {
- this.loading.groups = false
+ this.idState.loading.groups = false
}
},
@@ -623,7 +747,7 @@ export default {
if (group.canRemove === false) {
return false
}
- this.loading.groups = true
+ this.idState.loading.groups = true
const userid = this.user.id
const gid = group.id
try {
@@ -631,13 +755,13 @@ export default {
userid,
gid,
})
- this.loading.groups = false
+ this.idState.loading.groups = false
// remove user from current list if current list is the removed group
if (this.$route.params.selectedGroup === gid) {
this.$store.commit('deleteUser', userid)
}
} catch {
- this.loading.groups = false
+ this.idState.loading.groups = false
}
},
@@ -647,7 +771,7 @@ export default {
* @param {object} group Group object
*/
async addUserSubAdmin(group) {
- this.loading.subadmins = true
+ this.idState.loading.subadmins = true
const userid = this.user.id
const gid = group.id
try {
@@ -655,7 +779,7 @@ export default {
userid,
gid,
})
- this.loading.subadmins = false
+ this.idState.loading.subadmins = false
} catch (error) {
console.error(error)
}
@@ -667,7 +791,7 @@ export default {
* @param {object} group Group object
*/
async removeUserSubAdmin(group) {
- this.loading.subadmins = true
+ this.idState.loading.subadmins = true
const userid = this.user.id
const gid = group.id
@@ -679,7 +803,7 @@ export default {
} catch (error) {
console.error(error)
} finally {
- this.loading.subadmins = false
+ this.idState.loading.subadmins = false
}
},
@@ -692,9 +816,9 @@ export default {
async setUserQuota(quota = 'none') {
// Make sure correct label is set for unlimited quota
if (quota === 'none') {
- quota = this.unlimitedQuota
+ quota = unlimitedQuota
}
- this.loading.quota = true
+ this.idState.loading.quota = true
// ensure we only send the preset id
quota = quota.id ? quota.id : quota
@@ -707,7 +831,7 @@ export default {
} catch (error) {
console.error(error)
} finally {
- this.loading.quota = false
+ this.idState.loading.quota = false
}
return quota
},
@@ -725,7 +849,7 @@ export default {
// only used for new presets sent through @Tag
const validQuota = OC.Util.computerFileSize(quota)
if (validQuota === null) {
- return this.unlimitedQuota
+ return unlimitedQuota
} else {
// unify format output
quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota))
@@ -740,7 +864,7 @@ export default {
* @return {object}
*/
async setUserLanguage(lang) {
- this.loading.languages = true
+ this.idState.loading.languages = true
// ensure we only send the preset id
try {
await this.$store.dispatch('setUserData', {
@@ -748,7 +872,7 @@ export default {
key: 'language',
value: lang.code,
})
- this.loading.languages = false
+ this.idState.loading.languages = false
} catch (error) {
console.error(error)
}
@@ -759,48 +883,65 @@ export default {
* Dispatch new welcome mail request
*/
sendWelcomeMail() {
- this.loading.all = true
+ this.idState.loading.all = true
this.$store.dispatch('sendWelcomeMail', this.user.id)
.then(() => showSuccess(t('setting', 'Welcome mail sent!'), { timeout: 2000 }))
.finally(() => {
- this.loading.all = false
+ this.idState.loading.all = false
})
},
- toggleEdit() {
- this.editing = false
- if (this.editedDisplayName !== this.user.displayname) {
- this.editedDisplayName = this.user.displayname
- } else if (this.editedMail !== this.user.email) {
- this.editedMail = this.user.email
+ async toggleEdit() {
+ this.idState.editing = !this.idState.editing
+ if (this.idState.editing) {
+ await this.$nextTick()
+ this.$refs.displayNameField?.$refs?.inputField?.$refs?.input?.focus()
+ }
+ if (this.idState.editedDisplayName !== this.user.displayname) {
+ this.idState.editedDisplayName = this.user.displayname
+ } else if (this.idState.editedMail !== this.user.email) {
+ this.idState.editedMail = this.user.email ?? ''
}
},
},
}
</script>
-<style scoped lang="scss">
- // Force menu to be above other rows
- .row--menu-opened {
- z-index: 1 !important;
- }
- .row :deep() {
- .v-select.select {
- // reset min width to 100% instead of X px
- min-width: 100%;
- }
+<style lang="scss" scoped>
+@import './shared/styles.scss';
- .mailAddress,
- .password,
- .displayName {
+.row {
+ @include cell;
+
+ &__cell {
+ :deep {
.input-field,
+ .input-field__main-wrapper,
.input-field__input {
- height: 48px!important;
+ height: 48px !important;
}
+
.button-vue--icon-only {
- height: 44px!important;
+ height: 44px !important;
+ }
+
+ .v-select.select {
+ min-width: var(--cell-min-width);
}
}
- }
+ }
+ &__progress {
+ margin-top: 4px;
+
+ &--warn {
+ &::-moz-progress-bar {
+ background: var(--color-warning) !important;
+ }
+ &::-webkit-progress-value {
+ background: var(--color-warning) !important;
+ }
+ }
+ }
+}
</style>
diff --git a/apps/settings/src/components/Users/UserRowActions.vue b/apps/settings/src/components/Users/UserRowActions.vue
index ad89528fda7..4da5fd402fc 100644
--- a/apps/settings/src/components/Users/UserRowActions.vue
+++ b/apps/settings/src/components/Users/UserRowActions.vue
@@ -1,18 +1,44 @@
+<!--
+ - @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ -
+ - @author Christopher Ng <chrng8@gmail.com>
+ - @author Ferdinand Thiessen <opensource@fthiessen.de>
+ -
+ - @license AGPL-3.0-or-later
+ -
+ - 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/>.
+ -
+-->
+
<template>
<NcActions :aria-label="t('settings', 'Toggle user actions menu')"
+ :disabled="disabled"
:inline="1">
- <NcActionButton @click="toggleEdit">
+ <NcActionButton :disabled="disabled"
+ @click="toggleEdit">
{{ edit ? t('settings', 'Done') : t('settings', 'Edit') }}
<template #icon>
- <NcIconSvgWrapper :svg="editSvg" aria-hidden="true" />
+ <NcIconSvgWrapper :key="editSvg" :svg="editSvg" aria-hidden="true" />
</template>
</NcActionButton>
- <NcActionButton v-for="(action, index) in actions"
+ <NcActionButton v-for="({ action, icon, text }, index) in actions"
:key="index"
- :aria-label="action.text"
- :icon="action.icon"
- @click="action.action">
- {{ action.text }}
+ :disabled="disabled"
+ :aria-label="text"
+ :icon="icon"
+ @click="action">
+ {{ text }}
</NcActionButton>
</NcActions>
</template>
@@ -49,6 +75,14 @@ export default defineComponent({
},
/**
+ * The state whether the row is currently disabled
+ */
+ disabled: {
+ type: Boolean,
+ required: true,
+ },
+
+ /**
* The state whether the row is currently edited
*/
edit: {
diff --git a/apps/settings/src/components/Users/UserRowSimple.vue b/apps/settings/src/components/Users/UserRowSimple.vue
deleted file mode 100644
index 3d7f79b4195..00000000000
--- a/apps/settings/src/components/Users/UserRowSimple.vue
+++ /dev/null
@@ -1,185 +0,0 @@
-<template>
- <div class="row"
- :class="{'disabled': loading.delete || loading.disable}"
- :data-id="user.id">
- <div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
- <img v-if="!loading.delete && !loading.disable && !loading.wipe"
- alt=""
- width="32"
- height="32"
- :src="generateAvatar(user.id, isDarkTheme)">
- </div>
- <!-- dirty hack to ellipsis on two lines -->
- <div class="name">
- <div class="displayName subtitle">
- <div :title="user.displayname.length > 20 ? user.displayname : ''" class="cellText">
- <strong>
- {{ user.displayname }}
- </strong>
- </div>
- </div>
- {{ user.id }}
- </div>
- <div />
- <div class="mailAddress">
- <div :title="user.email !== null && user.email.length > 20 ? user.email : ''" class="cellText">
- {{ user.email }}
- </div>
- </div>
- <div class="groups">
- {{ userGroupsLabels }}
- </div>
- <div v-if="subAdminsGroups.length > 0 && settings.isAdmin" class="subAdminsGroups">
- {{ userSubAdminsGroupsLabels }}
- </div>
- <div class="userQuota">
- <div class="quota">
- {{ userQuota }} ({{ usedSpace }})
- <progress class="quota-user-progress"
- :class="{'warn': usedQuota > 80}"
- :value="usedQuota"
- max="100" />
- </div>
- </div>
- <div v-if="showConfig.showLanguages" class="languages">
- {{ userLanguage.name }}
- </div>
- <div v-if="showConfig.showUserBackend || showConfig.showStoragePath" class="userBackend">
- <div v-if="showConfig.showUserBackend" class="userBackend">
- {{ user.backend }}
- </div>
- <div v-if="showConfig.showStoragePath" :title="user.storageLocation" class="storageLocation subtitle">
- {{ user.storageLocation }}
- </div>
- </div>
- <div v-if="showConfig.showLastLogin" :title="userLastLoginTooltip" class="lastLogin">
- {{ userLastLogin }}
- </div>
- <div class="managers">
- {{ user.manager }}
- </div>
- <div class="userActions">
- <UserRowActions v-if="canEdit && !loading.all"
- :actions="userActions"
- :edit="false"
- @update:edit="toggleEdit" />
- </div>
- </div>
-</template>
-
-<script>
-import { getCurrentUser } from '@nextcloud/auth'
-
-import ClickOutside from 'vue-click-outside'
-
-import UserRowActions from './UserRowActions.vue'
-import UserRowMixin from '../../mixins/UserRowMixin.js'
-
-export default {
- name: 'UserRowSimple',
- components: {
- UserRowActions,
- },
- directives: {
- ClickOutside,
- },
- mixins: [UserRowMixin],
- props: {
- user: {
- type: Object,
- required: true,
- },
- loading: {
- type: Object,
- required: true,
- },
- showConfig: {
- type: Object,
- required: true,
- },
- userActions: {
- type: Array,
- required: true,
- },
- openedMenu: {
- type: Boolean,
- required: true,
- },
- subAdminsGroups: {
- type: Array,
- required: true,
- },
- settings: {
- type: Object,
- required: true,
- },
- isDarkTheme: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- userGroupsLabels() {
- return this.userGroups
- .map(group => group.name)
- .join(', ')
- },
- userSubAdminsGroupsLabels() {
- return this.userSubAdminsGroups
- .map(group => group.name)
- .join(', ')
- },
- usedSpace() {
- if (this.user.quota.used) {
- return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
- }
- return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
- },
- canEdit() {
- return getCurrentUser().uid !== this.user.id || this.settings.isAdmin
- },
- userQuota() {
- let quota = this.user.quota.quota
-
- if (quota === 'default') {
- quota = this.settings.defaultQuota
- if (quota !== 'none') {
- // convert to numeric value to match what the server would usually return
- quota = OC.Util.computerFileSize(quota)
- }
- }
-
- // when the default quota is unlimited, the server returns -3 here, map it to "none"
- if (quota === 'none' || quota === -3) {
- return t('settings', 'Unlimited')
- } else if (quota >= 0) {
- return OC.Util.humanFileSize(quota)
- }
- return OC.Util.humanFileSize(0)
- },
- },
- methods: {
- toggleMenu() {
- this.$emit('update:openedMenu', !this.openedMenu)
- },
- hideMenu() {
- this.$emit('update:openedMenu', false)
- },
- toggleEdit() {
- this.$emit('update:editing', true)
- },
- },
-}
-</script>
-
-<style lang="scss">
- .cellText {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
- .icon-more {
- background-color: var(--color-main-background);
- border: 0;
- }
-</style>
diff --git a/apps/settings/src/components/Users/shared/styles.scss b/apps/settings/src/components/Users/shared/styles.scss
new file mode 100644
index 00000000000..12525347738
--- /dev/null
+++ b/apps/settings/src/components/Users/shared/styles.scss
@@ -0,0 +1,110 @@
+/**
+ * @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ *
+ * @author Christopher Ng <chrng8@gmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+
+@mixin row {
+ position: absolute;
+ display: flex;
+ height: var(--row-height);
+ background-color: var(--color-main-background);
+}
+
+@mixin cell {
+ &__cell {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 0 var(--cell-padding);
+ width: var(--cell-width);
+ color: var(--color-main-text);
+
+ strong,
+ span,
+ label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow-wrap: anywhere;
+ }
+
+ @media (min-width: 670px) { /* Show one &--large column between stickied columns */
+ &--avatar,
+ &--displayname {
+ position: sticky;
+ z-index: 10;
+ background-color: var(--color-main-background);
+ }
+
+ &--avatar {
+ left: 0;
+ }
+
+ &--displayname {
+ left: var(--avatar-cell-width);
+ border-right: 1px solid var(--color-border);
+ }
+ }
+
+ &--avatar {
+ width: var(--avatar-cell-width);
+ align-items: center;
+ padding: 0;
+ user-select: none;
+ }
+
+ &--multiline {
+ span {
+ line-height: 1.3em;
+ white-space: unset;
+
+ @supports (-webkit-line-clamp: 2) {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ }
+ }
+ }
+
+ &--large {
+ width: 300px;
+ }
+
+ &--obfuscated {
+ width: 400px;
+ }
+
+ &--actions {
+ position: sticky;
+ right: 0;
+ z-index: 10;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ width: 110px;
+ background-color: var(--color-main-background);
+ border-left: 1px solid var(--color-border);
+ }
+ }
+
+ &__subtitle {
+ color: var(--color-text-maxcontrast);
+ }
+}
diff --git a/apps/settings/src/mixins/UserRowMixin.js b/apps/settings/src/mixins/UserRowMixin.js
index 23ed7a1ffab..f7e98c9a895 100644
--- a/apps/settings/src/mixins/UserRowMixin.js
+++ b/apps/settings/src/mixins/UserRowMixin.js
@@ -22,8 +22,6 @@
*
*/
-import { generateUrl } from '@nextcloud/router'
-
export default {
props: {
user: {
@@ -46,10 +44,6 @@ export default {
type: Array,
default: () => [],
},
- showConfig: {
- type: Object,
- default: () => ({}),
- },
languages: {
type: Array,
required: true,
@@ -60,6 +54,10 @@ export default {
},
},
computed: {
+ showConfig() {
+ return this.$store.getters.getShowConfig
+ },
+
/* GROUPS MANAGEMENT */
userGroups() {
const userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
@@ -153,32 +151,4 @@ export default {
return t('settings', 'Never')
},
},
- methods: {
- /**
- * Generate avatar url
- *
- * @param {string} user The user name
- * @param {bool} isDarkTheme Whether the avatar should be the dark version
- * @return {string}
- */
- generateAvatar(user, isDarkTheme) {
- if (isDarkTheme) {
- return generateUrl(
- '/avatar/{user}/64/dark?v={version}',
- {
- user,
- version: oc_userconfig.avatar.version,
- }
- )
- } else {
- return generateUrl(
- '/avatar/{user}/64?v={version}',
- {
- user,
- version: oc_userconfig.avatar.version,
- }
- )
- }
- },
- },
}
diff --git a/apps/settings/src/store/users.js b/apps/settings/src/store/users.js
index ab8105ecb51..b4af9851218 100644
--- a/apps/settings/src/store/users.js
+++ b/apps/settings/src/store/users.js
@@ -62,12 +62,22 @@ const state = {
usersOffset: 0,
usersLimit: 25,
userCount: 0,
+ showConfig: {
+ showStoragePath: false,
+ showUserBackend: false,
+ showLastLogin: false,
+ showNewUserForm: false,
+ showLanguages: false,
+ },
}
const mutations = {
appendUsers(state, usersObj) {
- // convert obj to array
- const users = state.users.concat(Object.keys(usersObj).map(userid => usersObj[userid]))
+ const existingUsers = state.users.map(({ id }) => id)
+ const newUsers = Object.values(usersObj)
+ .filter(({ id }) => !existingUsers.includes(id))
+
+ const users = state.users.concat(newUsers)
state.usersOffset += state.usersLimit
state.users = users
},
@@ -149,7 +159,7 @@ const mutations = {
},
addUserData(state, response) {
const user = response.data.ocs.data
- state.users.push(user)
+ state.users.unshift(user)
this.commit('updateUserCounts', { user, actionType: 'create' })
},
enableDisableUser(state, { userid, enabled }) {
@@ -221,6 +231,10 @@ const mutations = {
state.users = []
state.usersOffset = 0
},
+
+ setShowConfig(state, { key, value }) {
+ state.showConfig[key] = value
+ },
}
const getters = {
@@ -246,6 +260,9 @@ const getters = {
getUserCount(state) {
return state.userCount
},
+ getShowConfig(state) {
+ return state.showConfig
+ },
}
const CancelToken = axios.CancelToken
diff --git a/apps/settings/src/utils/userUtils.ts b/apps/settings/src/utils/userUtils.ts
new file mode 100644
index 00000000000..eff8315b693
--- /dev/null
+++ b/apps/settings/src/utils/userUtils.ts
@@ -0,0 +1,40 @@
+/**
+ * @copyright 2023 Christopher Ng <chrng8@gmail.com>
+ *
+ * @author Christopher Ng <chrng8@gmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+
+export const unlimitedQuota = {
+ id: 'none',
+ label: t('settings', 'Unlimited'),
+}
+
+export const defaultQuota = {
+ id: 'default',
+ label: t('settings', 'Default quota'),
+}
+
+/**
+ * Return `true` if the logged in user does not have permissions to view the
+ * data of `user`
+ */
+export const isObfuscated = (user: { id: string, [key: string]: any }) => {
+ const keys = Object.keys(user)
+ return keys.length === 1 && keys.at(0) === 'id'
+}
diff --git a/apps/settings/src/views/Users.vue b/apps/settings/src/views/Users.vue
index 6f8318a872a..e1f26ea6ea3 100644
--- a/apps/settings/src/views/Users.vue
+++ b/apps/settings/src/views/Users.vue
@@ -129,9 +129,7 @@
</template>
</NcAppNavigation>
<NcAppContent>
- <UserList :users="users"
- :show-config="showConfig"
- :selected-group="selectedGroupDecoded"
+ <UserList :selected-group="selectedGroupDecoded"
:external-actions="externalActions" />
</NcAppContent>
</NcContent>
@@ -160,6 +158,7 @@ import { generateUrl } from '@nextcloud/router'
import GroupListItem from '../components/GroupListItem.vue'
import UserList from '../components/UserList.vue'
+import { unlimitedQuota } from '../utils/userUtils.ts'
Vue.use(VueLocalStorage)
@@ -189,23 +188,17 @@ export default {
},
data() {
return {
- // default quota is set to unlimited
- unlimitedQuota: { id: 'none', label: t('settings', 'Unlimited') },
// temporary value used for multiselect change
selectedQuota: false,
externalActions: [],
loadingAddGroup: false,
loadingSendMail: false,
- showConfig: {
- showStoragePath: false,
- showUserBackend: false,
- showLastLogin: false,
- showNewUserForm: false,
- showLanguages: false,
- },
}
},
computed: {
+ showConfig() {
+ return this.$store.getters.getShowConfig
+ },
selectedGroupDecoded() {
return this.selectedGroup ? decodeURIComponent(this.selectedGroup) : null
},
@@ -224,25 +217,33 @@ export default {
// Local settings
showLanguages: {
- get() { return this.getLocalstorage('showLanguages') },
+ get() {
+ return this.getLocalstorage('showLanguages')
+ },
set(status) {
this.setLocalStorage('showLanguages', status)
},
},
showLastLogin: {
- get() { return this.getLocalstorage('showLastLogin') },
+ get() {
+ return this.getLocalstorage('showLastLogin')
+ },
set(status) {
this.setLocalStorage('showLastLogin', status)
},
},
showUserBackend: {
- get() { return this.getLocalstorage('showUserBackend') },
+ get() {
+ return this.getLocalstorage('showUserBackend')
+ },
set(status) {
this.setLocalStorage('showUserBackend', status)
},
},
showStoragePath: {
- get() { return this.getLocalstorage('showStoragePath') },
+ get() {
+ return this.getLocalstorage('showStoragePath')
+ },
set(status) {
this.setLocalStorage('showStoragePath', status)
},
@@ -261,7 +262,7 @@ export default {
const quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({ id: cur, label: cur }), [])
// add default presets
if (this.settings.allowUnlimitedQuota) {
- quotaPreset.unshift(this.unlimitedQuota)
+ quotaPreset.unshift(unlimitedQuota)
}
return quotaPreset
},
@@ -271,11 +272,11 @@ export default {
if (this.selectedQuota !== false) {
return this.selectedQuota
}
- if (this.settings.defaultQuota !== this.unlimitedQuota.id && OC.Util.computerFileSize(this.settings.defaultQuota) >= 0) {
+ if (this.settings.defaultQuota !== unlimitedQuota.id && OC.Util.computerFileSize(this.settings.defaultQuota) >= 0) {
// if value is valid, let's map the quotaOptions or return custom quota
return { id: this.settings.defaultQuota, label: this.settings.defaultQuota }
}
- return this.unlimitedQuota // unlimited
+ return unlimitedQuota // unlimited
},
set(quota) {
this.selectedQuota = quota
@@ -340,17 +341,20 @@ export default {
},
methods: {
showNewUserMenu() {
- this.showConfig.showNewUserForm = true
+ this.$store.commit('setShowConfig', {
+ key: 'showNewUserForm',
+ value: true,
+ })
},
getLocalstorage(key) {
// force initialization
const localConfig = this.$localStorage.get(key)
// if localstorage is null, fallback to original values
- this.showConfig[key] = localConfig !== null ? localConfig === 'true' : this.showConfig[key]
+ this.$store.commit('setShowConfig', { key, value: localConfig !== null ? localConfig === 'true' : this.showConfig[key] })
return this.showConfig[key]
},
setLocalStorage(key, status) {
- this.showConfig[key] = status
+ this.$store.commit('setShowConfig', { key, value: status })
this.$localStorage.set(key, status)
return status
},
@@ -363,7 +367,7 @@ export default {
setDefaultQuota(quota = 'none') {
// Make sure correct label is set for unlimited quota
if (quota === 'none') {
- quota = this.unlimitedQuota
+ quota = unlimitedQuota
}
this.$store.dispatch('setAppConfig', {
app: 'files',
@@ -391,7 +395,7 @@ export default {
// only used for new presets sent through @Tag
const validQuota = OC.Util.computerFileSize(quota)
if (validQuota === null) {
- return this.unlimitedQuota
+ return unlimitedQuota
} else {
// unify format output
quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota))
@@ -485,6 +489,14 @@ export default {
</script>
<style lang="scss" scoped>
+.app-content {
+ // Virtual list needs to be full height and is scrollable
+ display: flex;
+ overflow: hidden;
+ flex-direction: column;
+ max-height: 100%;
+}
+
// force hiding the editing action for the add group entry
.app-navigation__list #addgroup::v-deep .app-navigation-entry__utils {
display: none;
diff --git a/apps/sharebymail/l10n/gl.js b/apps/sharebymail/l10n/gl.js
index e862d8891c8..7788274ce99 100644
--- a/apps/sharebymail/l10n/gl.js
+++ b/apps/sharebymail/l10n/gl.js
@@ -12,28 +12,28 @@ OC.L10N.register(
"You unshared {file} from {email} by mail" : "Deixou de compartir {file} dende {email} por correo",
"{actor} unshared {file} from {email} by mail" : "{actor} deixou de compartir {file} dende {email} por correo",
"Password to access {file} was sent to {email}" : "Envióuselle a {email} un contrasinal para acceder a {file}",
- "Password to access {file} was sent to you" : "Envióuselle a vostede un correo para acceder a {file}",
+ "Password to access {file} was sent to you" : "Envióuselle a Vde. un correo para acceder a {file}",
"Share by mail" : "Compartido por correo",
"Sharing %1$s failed, because this item is already shared with user %2$s" : "Fallou a compartición de %1$s por mor de que este elemento xa foi compartido co usuario %2$s",
"We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again." : "Non é posíbel enviarlle o contrasinal xerado automaticamente. Estabeleza un enderezo de correo correcto nos seus axustes persoais e ténteo de novo.",
"Failed to send share by email. Got an invalid email address" : "Produciuse un erro ao enviar o recurso compartido por correo. O enderezo de correo non é válido",
"Failed to send share by email" : "Produciuse un fallo ao enviar o recurso compartido por correo",
- "%1$s shared »%2$s« with you" : "%1$s compartiu «%2$s» con vostede",
- "%1$s shared »%2$s« with you." : "%1$s compartiu «%2$s» con vostede.",
+ "%1$s shared »%2$s« with you" : "%1$s compartiu «%2$s» con Vde.",
+ "%1$s shared »%2$s« with you." : "%1$s compartiu «%2$s» con Vde.",
"Click the button below to open it." : "Prema no botón de embaixo para abrilo.",
"Open »%s«" : "Abrir «%s»",
"%1$s via %2$s" : "%1$s mediante %2$s",
- "%1$s shared »%2$s« with you.\nYou should have already received a separate mail with a link to access it.\n" : "%1$s compartiu «%2$s» con vostede.\nDebería ter recibido un correo por separado cunha ligazón acceder.\n",
- "%1$s shared »%2$s« with you. You should have already received a separate mail with a link to access it." : "%1$s compartiu «%2$s» con vostede. Debería ter recibido un correo por separado cunha ligazón acceder.",
- "Password to access »%1$s« shared to you by %2$s" : "O contrasinal para acceder a «%1$s» foi compartido con vostede por %2$s",
+ "%1$s shared »%2$s« with you.\nYou should have already received a separate mail with a link to access it.\n" : "%1$s compartiu «%2$s» con Vde.\nDebería ter recibido un correo por separado cunha ligazón acceder.\n",
+ "%1$s shared »%2$s« with you. You should have already received a separate mail with a link to access it." : "%1$s compartiu «%2$s» con Vde. Debería ter recibido un correo por separado cunha ligazón acceder.",
+ "Password to access »%1$s« shared to you by %2$s" : "O contrasinal para acceder a «%1$s» foi compartido con Vde. por %2$s",
"Password to access »%s«" : "Contrasinal para acceder a «%s»",
"It is protected with the following password:" : "Está protexido co seguinte contrasinal: ",
"This password will expire at %s" : "Este contrasinal caducará o %s",
- "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartiu «%2$s» con vostede e quere engadir:",
- "%1$s shared »%2$s« with you and wants to add" : "%1$s compartiu «%2$s» con vostede e quere engadir",
- "»%s« added a note to a file shared with you" : "«%s» engadiu unha nota a un ficheiro compartido con vostede",
- "You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Ven de de compartir «%1$s» con %2$s. O recurso compartido xa foi enviado ao destinatario. Por mor das directivas de seguranza definidas polo administrador de %3$s cada recurso compartido necesita ser protexido por un contrasinal e non está permitido que vostede envíe o contrasinal directamente ao destinatario. Daquela, necesita enviar manualmente o contrasinal ao destinatario.",
- "Password to access »%1$s« shared by you with %2$s" : "Contrasinal para acceder a «%1$s» compartida por vostede con %2$s",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartiu «%2$s» con Vde. e quere engadir:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s compartiu «%2$s» con Vde. e quere engadir",
+ "»%s« added a note to a file shared with you" : "«%s» engadiu unha nota a un ficheiro compartido con Vde.",
+ "You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Ven de de compartir «%1$s» con %2$s. O recurso compartido xa foi enviado ao destinatario. Por mor das directivas de seguranza definidas polo administrador de %3$s cada recurso compartido necesita ser protexido por un contrasinal e non está permitido que Vde. envíe o contrasinal directamente ao destinatario. Daquela, necesita enviar manualmente o contrasinal ao destinatario.",
+ "Password to access »%1$s« shared by you with %2$s" : "Contrasinal para acceder a «%1$s» compartida por Vde. con %2$s",
"This is the password:" : "Este é o contrasinal:",
"You can choose a different password at any time in the share dialog." : "Pode escoller un contrasinal diferente en calquera momento no diálogo de compartir.",
"Could not find share" : "Non foi posíbel atopar o recurso compartido",
@@ -52,6 +52,6 @@ OC.L10N.register(
"You unshared %1$s from %2$s by mail" : "Deixou de compartir %1$s dende %2$s por correo",
"%3$s unshared %1$s from %2$s by mail" : "%3$s deixou de compartir %1$s dende %2$s por correo",
"Password to access %1$s was sent to %2s" : "Envióuselle a %2s un contrasinal para acceder a %1$s",
- "Password to access %1$s was sent to you" : "Envióuselle a vostede un correo para acceder a %1$s"
+ "Password to access %1$s was sent to you" : "Envióuselle a Vde. un correo para acceder a %1$s"
},
"nplurals=2; plural=(n != 1);");
diff --git a/apps/sharebymail/l10n/gl.json b/apps/sharebymail/l10n/gl.json
index 37d826245b8..ea400dcaf06 100644
--- a/apps/sharebymail/l10n/gl.json
+++ b/apps/sharebymail/l10n/gl.json
@@ -10,28 +10,28 @@
"You unshared {file} from {email} by mail" : "Deixou de compartir {file} dende {email} por correo",
"{actor} unshared {file} from {email} by mail" : "{actor} deixou de compartir {file} dende {email} por correo",
"Password to access {file} was sent to {email}" : "Envióuselle a {email} un contrasinal para acceder a {file}",
- "Password to access {file} was sent to you" : "Envióuselle a vostede un correo para acceder a {file}",
+ "Password to access {file} was sent to you" : "Envióuselle a Vde. un correo para acceder a {file}",
"Share by mail" : "Compartido por correo",
"Sharing %1$s failed, because this item is already shared with user %2$s" : "Fallou a compartición de %1$s por mor de que este elemento xa foi compartido co usuario %2$s",
"We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again." : "Non é posíbel enviarlle o contrasinal xerado automaticamente. Estabeleza un enderezo de correo correcto nos seus axustes persoais e ténteo de novo.",
"Failed to send share by email. Got an invalid email address" : "Produciuse un erro ao enviar o recurso compartido por correo. O enderezo de correo non é válido",
"Failed to send share by email" : "Produciuse un fallo ao enviar o recurso compartido por correo",
- "%1$s shared »%2$s« with you" : "%1$s compartiu «%2$s» con vostede",
- "%1$s shared »%2$s« with you." : "%1$s compartiu «%2$s» con vostede.",
+ "%1$s shared »%2$s« with you" : "%1$s compartiu «%2$s» con Vde.",
+ "%1$s shared »%2$s« with you." : "%1$s compartiu «%2$s» con Vde.",
"Click the button below to open it." : "Prema no botón de embaixo para abrilo.",
"Open »%s«" : "Abrir «%s»",
"%1$s via %2$s" : "%1$s mediante %2$s",
- "%1$s shared »%2$s« with you.\nYou should have already received a separate mail with a link to access it.\n" : "%1$s compartiu «%2$s» con vostede.\nDebería ter recibido un correo por separado cunha ligazón acceder.\n",
- "%1$s shared »%2$s« with you. You should have already received a separate mail with a link to access it." : "%1$s compartiu «%2$s» con vostede. Debería ter recibido un correo por separado cunha ligazón acceder.",
- "Password to access »%1$s« shared to you by %2$s" : "O contrasinal para acceder a «%1$s» foi compartido con vostede por %2$s",
+ "%1$s shared »%2$s« with you.\nYou should have already received a separate mail with a link to access it.\n" : "%1$s compartiu «%2$s» con Vde.\nDebería ter recibido un correo por separado cunha ligazón acceder.\n",
+ "%1$s shared »%2$s« with you. You should have already received a separate mail with a link to access it." : "%1$s compartiu «%2$s» con Vde. Debería ter recibido un correo por separado cunha ligazón acceder.",
+ "Password to access »%1$s« shared to you by %2$s" : "O contrasinal para acceder a «%1$s» foi compartido con Vde. por %2$s",
"Password to access »%s«" : "Contrasinal para acceder a «%s»",
"It is protected with the following password:" : "Está protexido co seguinte contrasinal: ",
"This password will expire at %s" : "Este contrasinal caducará o %s",
- "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartiu «%2$s» con vostede e quere engadir:",
- "%1$s shared »%2$s« with you and wants to add" : "%1$s compartiu «%2$s» con vostede e quere engadir",
- "»%s« added a note to a file shared with you" : "«%s» engadiu unha nota a un ficheiro compartido con vostede",
- "You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Ven de de compartir «%1$s» con %2$s. O recurso compartido xa foi enviado ao destinatario. Por mor das directivas de seguranza definidas polo administrador de %3$s cada recurso compartido necesita ser protexido por un contrasinal e non está permitido que vostede envíe o contrasinal directamente ao destinatario. Daquela, necesita enviar manualmente o contrasinal ao destinatario.",
- "Password to access »%1$s« shared by you with %2$s" : "Contrasinal para acceder a «%1$s» compartida por vostede con %2$s",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s compartiu «%2$s» con Vde. e quere engadir:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s compartiu «%2$s» con Vde. e quere engadir",
+ "»%s« added a note to a file shared with you" : "«%s» engadiu unha nota a un ficheiro compartido con Vde.",
+ "You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Ven de de compartir «%1$s» con %2$s. O recurso compartido xa foi enviado ao destinatario. Por mor das directivas de seguranza definidas polo administrador de %3$s cada recurso compartido necesita ser protexido por un contrasinal e non está permitido que Vde. envíe o contrasinal directamente ao destinatario. Daquela, necesita enviar manualmente o contrasinal ao destinatario.",
+ "Password to access »%1$s« shared by you with %2$s" : "Contrasinal para acceder a «%1$s» compartida por Vde. con %2$s",
"This is the password:" : "Este é o contrasinal:",
"You can choose a different password at any time in the share dialog." : "Pode escoller un contrasinal diferente en calquera momento no diálogo de compartir.",
"Could not find share" : "Non foi posíbel atopar o recurso compartido",
@@ -50,6 +50,6 @@
"You unshared %1$s from %2$s by mail" : "Deixou de compartir %1$s dende %2$s por correo",
"%3$s unshared %1$s from %2$s by mail" : "%3$s deixou de compartir %1$s dende %2$s por correo",
"Password to access %1$s was sent to %2s" : "Envióuselle a %2s un contrasinal para acceder a %1$s",
- "Password to access %1$s was sent to you" : "Envióuselle a vostede un correo para acceder a %1$s"
+ "Password to access %1$s was sent to you" : "Envióuselle a Vde. un correo para acceder a %1$s"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
} \ No newline at end of file
diff --git a/apps/systemtags/l10n/gl.js b/apps/systemtags/l10n/gl.js
index 0c2c7d82cb0..46c7b6eaca0 100644
--- a/apps/systemtags/l10n/gl.js
+++ b/apps/systemtags/l10n/gl.js
@@ -15,28 +15,28 @@ OC.L10N.register(
"Removed system tag %1$s" : "Retirada a etiqueta de sistema %1$s",
"%1$s removed system tag %2$s" : "%1$s retirou a etiqueta de sistema %2$s",
"{actor} removed system tag {systemtag}" : "{actor} retirou a etiqueta de sistema {systemtag}",
- "You created system tag %1$s" : "Vostede creou a etiqueta de sistema %1$s",
- "You created system tag {systemtag}" : "Vostede creou a etiqueta de sistema {systemtag}",
+ "You created system tag %1$s" : "Vde. creou a etiqueta de sistema %1$s",
+ "You created system tag {systemtag}" : "Vde. creou a etiqueta de sistema {systemtag}",
"%1$s created system tag %2$s" : "%1$s creou a etiqueta de sistema %2$s",
"{actor} created system tag {systemtag}" : "{actor} creou a etiqueta de sistema {systemtag}",
- "You deleted system tag %1$s" : "Vostede eliminou a etiqueta de sistema %1$s",
- "You deleted system tag {systemtag}" : "Vostede eliminou a etiqueta de sistema {systemtag}",
+ "You deleted system tag %1$s" : "Vde. eliminou a etiqueta de sistema %1$s",
+ "You deleted system tag {systemtag}" : "Vde. eliminou a etiqueta de sistema {systemtag}",
"%1$s deleted system tag %2$s" : "%1$s eliminou a etiqueta de sistema %2$s",
"{actor} deleted system tag {systemtag}" : "{actor} eliminou a etiqueta de sistema {systemtag}",
- "You updated system tag %2$s to %1$s" : "Vostede actualizou a etiqueta de sistema %2$s a %1$s",
- "You updated system tag {oldsystemtag} to {newsystemtag}" : "Vostede actualizou a etiqueta de sistema {oldsystemtag} a {newsystemtag}",
+ "You updated system tag %2$s to %1$s" : "Vde. actualizou a etiqueta de sistema %2$s a %1$s",
+ "You updated system tag {oldsystemtag} to {newsystemtag}" : "Vde. actualizou a etiqueta de sistema {oldsystemtag} a {newsystemtag}",
"%1$s updated system tag %3$s to %2$s" : "%1$s actualizou a etiqueta de sistema %3$s a %2$s",
"{actor} updated system tag {oldsystemtag} to {newsystemtag}" : "{actor} actualizou a etiqueta de sistema {oldsystemtag} a {newsystemtag}",
"System tag %2$s was added to %1$s by the system" : "Etiqueta de sistema %2$sfoi engadida a %1$s polo sistema",
"System tag {systemtag} was added to {file} by the system" : "Etiqueta de sistema {systemtag} foi engadida a {file} polo sistema",
- "You added system tag %2$s to %1$s" : "Vostede engadiu a etiqueta de sistema %2$s a %1$s",
- "You added system tag {systemtag} to {file}" : "Vostede engadiu a etiqueta de sistema {systemtag} a {file}",
+ "You added system tag %2$s to %1$s" : "Vde. engadiu a etiqueta de sistema %2$s a %1$s",
+ "You added system tag {systemtag} to {file}" : "Vde. engadiu a etiqueta de sistema {systemtag} a {file}",
"%1$s added system tag %3$s to %2$s" : "%1$s engadiu a etiqueta de sistema %3$s a %2$s",
"{actor} added system tag {systemtag} to {file}" : "{actor} engadiu a etiqueta de sistema {systemtag} a {file}",
"System tag %2$s was removed from %1$s by the system" : "Etiqueta de sistema %2$s retirada de %1$s polo sistema",
"System tag {systemtag} was removed from {file} by the system" : "Etiqueta de sistema {systemtag} retirada de {file} polo sistema",
- "You removed system tag %2$s from %1$s" : "Vostede retirou a etiqueta de sistema %2$s de %1$s",
- "You removed system tag {systemtag} from {file}" : "Vostede retirou a etiqueta de sistema {systemtag} de {file}",
+ "You removed system tag %2$s from %1$s" : "Vde. retirou a etiqueta de sistema %2$s de %1$s",
+ "You removed system tag {systemtag} from {file}" : "Vde. retirou a etiqueta de sistema {systemtag} de {file}",
"%1$s removed system tag %3$s from %2$s" : "%1$s retirou a etiqueta de sistema %3$s de %2$s",
"{actor} removed system tag {systemtag} from {file}" : "{actor} retirou a etiqueta de sistema {systemtag} de {file}",
"%s (restricted)" : "%s (restrinxida)",
@@ -47,17 +47,17 @@ OC.L10N.register(
"tagged %s" : "etiquetado %s",
"Collaborative tags" : "Etiquetas colaborativas",
"Collaborative tagging functionality which shares tags among users." : "Funcionalidade de etiquetado colaborativo que comparte as etiquetas entre usuarios.",
- "Collaborative tagging functionality which shares tags among users. Great for teams.\n\t(If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.)" : "Funcionalidade de etiquetado colaborativo que comparte as etiquetas entre usuarios. Moi axeitado para equipos.\n(Se vostede é un provedor cunha instalación de varias instalacións, recoméndase desactivar esta aplicación xa que as etiquetas compártense).",
+ "Collaborative tagging functionality which shares tags among users. Great for teams.\n\t(If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.)" : "Funcionalidade de etiquetado colaborativo que comparte as etiquetas entre usuarios. Moi axeitado para equipos.\n(Se Vde. é un provedor cunha instalación de varias instalacións, recoméndase desactivar esta aplicación xa que as etiquetas compártense).",
"Loading collaborative tags …" : "Cargando etiquetas colaborativas…",
"Search or create collaborative tags" : "Buscar ou crear etiquetas colaborativas",
"Collaborative tags …" : "Etiquetas colaborativas…",
"No tags to select, type to create a new tag" : "Non hai etiquetas que seleccionar, escriba para crear unha nova",
"Failed to load tags" : "Produciuse un fallo ao cargar as etiquetas",
- "Failed to load selected tags" : "Produciuse un erro ao cargar as etiquetas seleccionadas",
- "Failed to select tag" : "Produciuse un erro ao seleccionar a etiqueta",
- "Failed to create tag" : "Produciuse un erro ao crear a etiqueta",
- "Failed to delete tag" : "Produciuse un erro ao eliminar a etiqueta",
- "Failed to load last used tags" : "Produciuse un erro ao cargar as últimas etiquetas utilizadas",
+ "Failed to load selected tags" : "Produciuse un fallo ao cargar as etiquetas seleccionadas",
+ "Failed to select tag" : "Produciuse un fallo ao seleccionar a etiqueta",
+ "Failed to create tag" : "Produciuse un fallo ao crear a etiqueta",
+ "Failed to delete tag" : "Produciuse un fallo ao eliminar a etiqueta",
+ "Failed to load last used tags" : "Produciuse un fallo ao cargar as últimas etiquetas utilizadas",
"Missing \"Content-Location\" header" : "Falta a cabeceira «Content-Location».",
"Tagged files" : "Ficheiros etiquetados",
"Select tags to filter by" : "Seleccionar etiquetas polas que filtrar",
diff --git a/apps/systemtags/l10n/gl.json b/apps/systemtags/l10n/gl.json
index 7e668b7f5f7..f9ea76b7b81 100644
--- a/apps/systemtags/l10n/gl.json
+++ b/apps/systemtags/l10n/gl.json
@@ -13,28 +13,28 @@
"Removed system tag %1$s" : "Retirada a etiqueta de sistema %1$s",
"%1$s removed system tag %2$s" : "%1$s retirou a etiqueta de sistema %2$s",
"{actor} removed system tag {systemtag}" : "{actor} retirou a etiqueta de sistema {systemtag}",
- "You created system tag %1$s" : "Vostede creou a etiqueta de sistema %1$s",
- "You created system tag {systemtag}" : "Vostede creou a etiqueta de sistema {systemtag}",
+ "You created system tag %1$s" : "Vde. creou a etiqueta de sistema %1$s",
+ "You created system tag {systemtag}" : "Vde. creou a etiqueta de sistema {systemtag}",
"%1$s created system tag %2$s" : "%1$s creou a etiqueta de sistema %2$s",
"{actor} created system tag {systemtag}" : "{actor} creou a etiqueta de sistema {systemtag}",
- "You deleted system tag %1$s" : "Vostede eliminou a etiqueta de sistema %1$s",
- "You deleted system tag {systemtag}" : "Vostede eliminou a etiqueta de sistema {systemtag}",
+ "You deleted system tag %1$s" : "Vde. eliminou a etiqueta de sistema %1$s",
+ "You deleted system tag {systemtag}" : "Vde. eliminou a etiqueta de sistema {systemtag}",
"%1$s deleted system tag %2$s" : "%1$s eliminou a etiqueta de sistema %2$s",
"{actor} deleted system tag {systemtag}" : "{actor} eliminou a etiqueta de sistema {systemtag}",
- "You updated system tag %2$s to %1$s" : "Vostede actualizou a etiqueta de sistema %2$s a %1$s",
- "You updated system tag {oldsystemtag} to {newsystemtag}" : "Vostede actualizou a etiqueta de sistema {oldsystemtag} a {newsystemtag}",
+ "You updated system tag %2$s to %1$s" : "Vde. actualizou a etiqueta de sistema %2$s a %1$s",
+ "You updated system tag {oldsystemtag} to {newsystemtag}" : "Vde. actualizou a etiqueta de sistema {oldsystemtag} a {newsystemtag}",
"%1$s updated system tag %3$s to %2$s" : "%1$s actualizou a etiqueta de sistema %3$s a %2$s",
"{actor} updated system tag {oldsystemtag} to {newsystemtag}" : "{actor} actualizou a etiqueta de sistema {oldsystemtag} a {newsystemtag}",
"System tag %2$s was added to %1$s by the system" : "Etiqueta de sistema %2$sfoi engadida a %1$s polo sistema",
"System tag {systemtag} was added to {file} by the system" : "Etiqueta de sistema {systemtag} foi engadida a {file} polo sistema",
- "You added system tag %2$s to %1$s" : "Vostede engadiu a etiqueta de sistema %2$s a %1$s",
- "You added system tag {systemtag} to {file}" : "Vostede engadiu a etiqueta de sistema {systemtag} a {file}",
+ "You added system tag %2$s to %1$s" : "Vde. engadiu a etiqueta de sistema %2$s a %1$s",
+ "You added system tag {systemtag} to {file}" : "Vde. engadiu a etiqueta de sistema {systemtag} a {file}",
"%1$s added system tag %3$s to %2$s" : "%1$s engadiu a etiqueta de sistema %3$s a %2$s",
"{actor} added system tag {systemtag} to {file}" : "{actor} engadiu a etiqueta de sistema {systemtag} a {file}",
"System tag %2$s was removed from %1$s by the system" : "Etiqueta de sistema %2$s retirada de %1$s polo sistema",
"System tag {systemtag} was removed from {file} by the system" : "Etiqueta de sistema {systemtag} retirada de {file} polo sistema",
- "You removed system tag %2$s from %1$s" : "Vostede retirou a etiqueta de sistema %2$s de %1$s",
- "You removed system tag {systemtag} from {file}" : "Vostede retirou a etiqueta de sistema {systemtag} de {file}",
+ "You removed system tag %2$s from %1$s" : "Vde. retirou a etiqueta de sistema %2$s de %1$s",
+ "You removed system tag {systemtag} from {file}" : "Vde. retirou a etiqueta de sistema {systemtag} de {file}",
"%1$s removed system tag %3$s from %2$s" : "%1$s retirou a etiqueta de sistema %3$s de %2$s",
"{actor} removed system tag {systemtag} from {file}" : "{actor} retirou a etiqueta de sistema {systemtag} de {file}",
"%s (restricted)" : "%s (restrinxida)",
@@ -45,17 +45,17 @@
"tagged %s" : "etiquetado %s",
"Collaborative tags" : "Etiquetas colaborativas",
"Collaborative tagging functionality which shares tags among users." : "Funcionalidade de etiquetado colaborativo que comparte as etiquetas entre usuarios.",
- "Collaborative tagging functionality which shares tags among users. Great for teams.\n\t(If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.)" : "Funcionalidade de etiquetado colaborativo que comparte as etiquetas entre usuarios. Moi axeitado para equipos.\n(Se vostede é un provedor cunha instalación de varias instalacións, recoméndase desactivar esta aplicación xa que as etiquetas compártense).",
+ "Collaborative tagging functionality which shares tags among users. Great for teams.\n\t(If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.)" : "Funcionalidade de etiquetado colaborativo que comparte as etiquetas entre usuarios. Moi axeitado para equipos.\n(Se Vde. é un provedor cunha instalación de varias instalacións, recoméndase desactivar esta aplicación xa que as etiquetas compártense).",
"Loading collaborative tags …" : "Cargando etiquetas colaborativas…",
"Search or create collaborative tags" : "Buscar ou crear etiquetas colaborativas",
"Collaborative tags …" : "Etiquetas colaborativas…",
"No tags to select, type to create a new tag" : "Non hai etiquetas que seleccionar, escriba para crear unha nova",
"Failed to load tags" : "Produciuse un fallo ao cargar as etiquetas",
- "Failed to load selected tags" : "Produciuse un erro ao cargar as etiquetas seleccionadas",
- "Failed to select tag" : "Produciuse un erro ao seleccionar a etiqueta",
- "Failed to create tag" : "Produciuse un erro ao crear a etiqueta",
- "Failed to delete tag" : "Produciuse un erro ao eliminar a etiqueta",
- "Failed to load last used tags" : "Produciuse un erro ao cargar as últimas etiquetas utilizadas",
+ "Failed to load selected tags" : "Produciuse un fallo ao cargar as etiquetas seleccionadas",
+ "Failed to select tag" : "Produciuse un fallo ao seleccionar a etiqueta",
+ "Failed to create tag" : "Produciuse un fallo ao crear a etiqueta",
+ "Failed to delete tag" : "Produciuse un fallo ao eliminar a etiqueta",
+ "Failed to load last used tags" : "Produciuse un fallo ao cargar as últimas etiquetas utilizadas",
"Missing \"Content-Location\" header" : "Falta a cabeceira «Content-Location».",
"Tagged files" : "Ficheiros etiquetados",
"Select tags to filter by" : "Seleccionar etiquetas polas que filtrar",
diff --git a/apps/updatenotification/src/components/UpdateNotification.vue b/apps/updatenotification/src/components/UpdateNotification.vue
index 88ea8e50791..c8374a12195 100644
--- a/apps/updatenotification/src/components/UpdateNotification.vue
+++ b/apps/updatenotification/src/components/UpdateNotification.vue
@@ -7,13 +7,15 @@
</NcNoteCard>
<p>
+ <!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="newVersionAvailableString" /><br>
<span v-if="!isListFetched" class="icon icon-loading-small" />
+ <!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="statusText" />
</p>
<template v-if="missingAppUpdates.length">
- <h3 @click="toggleHideMissingUpdates">
+ <h3 class="clickable" @click="toggleHideMissingUpdates">
{{ t('updatenotification', 'Apps missing compatible version') }}
<span v-if="!hideMissingUpdates" class="icon icon-triangle-n" />
<span v-if="hideMissingUpdates" class="icon icon-triangle-s" />
@@ -26,7 +28,7 @@
</template>
<template v-if="availableAppUpdates.length">
- <h3 @click="toggleHideAvailableUpdates">
+ <h3 class="clickable" @click="toggleHideAvailableUpdates">
{{ t('updatenotification', 'Apps with compatible version') }}
<span v-if="!hideAvailableUpdates" class="icon icon-triangle-n" />
<span v-if="hideAvailableUpdates" class="icon icon-triangle-s" />
@@ -56,14 +58,26 @@
<span v-if="updaterEnabled && !webUpdaterEnabled">
{{ t('updatenotification', 'Please use the command line updater to update.') }}
</span>
- <div v-if="whatsNew" class="whatsNew">
- <div class="toggleWhatsNew">
- <a v-click-outside="hideMenu" class="button" @click="toggleMenu">{{ t('updatenotification', 'What\'s new?') }}</a>
- <div class="popovermenu" :class="{ 'menu-center': true, open: openedWhatsNew }">
- <NcPopoverMenu :menu="whatsNew" />
- </div>
- </div>
- </div>
+ <NcActions v-if="whatsNewData || changelogURL"
+ :force-menu="true"
+ :menu-title="t('updatenotification', 'What\'s new?')"
+ type="tertiary">
+ <template #icon>
+ <IconNewBox :size="20" />
+ </template>
+ <template #default>
+ <NcActionCaption v-for="changes,index in whatsNewData" :key="index" :title="changes" />
+ <NcActionLink v-if="changelogURL"
+ :href="changelogURL"
+ close-after-click
+ target="_blank">
+ {{ t('updatenotification', 'View changelog') }}
+ <template #icon>
+ <IconLink :size="20" />
+ </template>
+ </NcActionLink>
+ </template>
+ </NcActions>
</div>
</template>
<template v-else-if="!isUpdateChecked">
@@ -81,61 +95,80 @@
</template>
</div>
- <div>
- {{ t('updatenotification', 'You can change the update channel below which also affects the apps management page. E.g. after switching to the beta channel, beta app updates will be offered to you in the apps management page.') }}
+ <h3>{{ t('updatenotification', 'Update channel') }}</h3>
+ <p class="inlineblock">
+ {{ t('updatenotification', 'Changing the update channel also affects the apps management page. E.g. after switching to the beta channel, beta app updates will be offered to you in the apps management page.') }}
+ </p>
+ <div class="update-channel-selector">
+ <span>{{ t('updatenotification', 'Current update channel:') }}</span>
+ <NcActions :force-menu="true"
+ :menu-title="localizedChannelName"
+ type="tertiary">
+ <template #icon>
+ <IconChevronDown :size="20" />
+ </template>
+ <template #default>
+ <!-- TODO use NcActionRadio if it provides long text, e.g. subtitle -->
+ <NcActionButton v-for="channel,index in channelList"
+ :key="index"
+ :aria-checked="channel.active ? 'true' : 'false'"
+ :aria-label="channel.text"
+ :disabled="!!channel.disabled"
+ :icon="channel.icon"
+ :name="channel.text"
+ class="update-channel-action"
+ close-after-click
+ role="menuitemradio"
+ @click="channel.action">
+ {{ channel.longtext }}
+ </NcActionButton>
+ </template>
+ </NcActions>
</div>
-
- <h3 class="update-channel-selector">
- {{ t('updatenotification', 'Update channel:') }}
- <div v-click-outside="closeUpdateChannelMenu" class="update-menu">
- <span class="icon-update-menu" @click="toggleUpdateChannelMenu">
- {{ localizedChannelName }}
- <span class="icon-triangle-s" />
- </span>
- <div class="popovermenu menu menu-center" :class="{ 'show-menu': openedUpdateChannelMenu}">
- <NcPopoverMenu :menu="channelList" />
- </div>
- </div>
- </h3>
- <span id="channel_save_msg" class="msg" /><br>
<p>
<em>{{ t('updatenotification', 'You can always update to a newer version. But you can never downgrade to a more stable version.') }}</em><br>
+ <!-- eslint-disable-next-line vue/no-v-html -->
<em v-html="noteDelayedStableString" />
</p>
- <p id="oca_updatenotification_groups">
- {{ t('updatenotification', 'Notify members of the following groups about available updates:') }}
- <NcSelect v-model="notifyGroups"
- :options="groups"
- :multiple="true"
- label="displayname"
- :loading="loadingGroups"
- :close-on-select="false"
- @search="searchGroup">
- <template #no-options>
- {{ t('updatenotification', 'No groups') }}
- </template>
- </NcSelect>
- <br>
+ <h4>{{ t('updatenotification', 'Notify members of the following groups about available updates:') }}</h4>
+ <NcSelect v-model="notifyGroups"
+ :options="groups"
+ :multiple="true"
+ label="displayname"
+ :loading="loadingGroups"
+ :close-on-select="false"
+ @search="searchGroup">
+ <template #no-options>
+ {{ t('updatenotification', 'No groups') }}
+ </template>
+ </NcSelect>
+ <p>
<em v-if="currentChannel === 'daily' || currentChannel === 'git'">{{ t('updatenotification', 'Only notifications for app updates are available.') }}</em>
<em v-if="currentChannel === 'daily'">{{ t('updatenotification', 'The selected update channel makes dedicated notifications for the server obsolete.') }}</em>
- <em v-if="currentChannel === 'git'">{{ t('updatenotification', 'The selected update channel does not support updates of the server.') }}</em>
+ <em v-else-if="currentChannel === 'git'">{{ t('updatenotification', 'The selected update channel does not support updates of the server.') }}</em>
</p>
</NcSettingsSection>
</template>
<script>
+import { showSuccess } from '@nextcloud/dialogs'
+import { loadState } from '@nextcloud/initial-state'
+import { getLoggerBuilder } from '@nextcloud/logger'
import { generateUrl, getRootUrl, generateOcsUrl } from '@nextcloud/router'
-import NcPopoverMenu from '@nextcloud/vue/dist/Components/NcPopoverMenu.js'
+
+import axios from '@nextcloud/axios'
+import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
+import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
+import NcActionCaption from '@nextcloud/vue/dist/Components/NcActionCaption.js'
+import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
+import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'
-import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
-import ClickOutside from 'vue-click-outside'
-import axios from '@nextcloud/axios'
-import { loadState } from '@nextcloud/initial-state'
-import { showSuccess } from '@nextcloud/dialogs'
+import IconChevronDown from 'vue-material-design-icons/ChevronDown.vue'
+import IconLink from 'vue-material-design-icons/Link.vue'
+import IconNewBox from 'vue-material-design-icons/NewBox.vue'
import debounce from 'debounce'
-import { getLoggerBuilder } from '@nextcloud/logger'
const logger = getLoggerBuilder()
.setApp('updatenotification')
@@ -145,13 +178,16 @@ const logger = getLoggerBuilder()
export default {
name: 'UpdateNotification',
components: {
+ IconChevronDown,
+ IconLink,
+ IconNewBox,
+ NcActions,
+ NcActionButton,
+ NcActionCaption,
+ NcActionLink,
+ NcNoteCard,
NcSelect,
- NcPopoverMenu,
NcSettingsSection,
- NcNoteCard,
- },
- directives: {
- ClickOutside,
},
data() {
return {
@@ -224,26 +260,6 @@ export default {
: n('updatenotification', '<strong>%n</strong> app has no compatible version for this Nextcloud version available.', '<strong>%n</strong> apps have no compatible version for this Nextcloud version available.', this.missingAppUpdates.length)
},
- whatsNew() {
- if (this.whatsNewData.length === 0) {
- return null
- }
- const whatsNew = []
- for (const i in this.whatsNewData) {
- whatsNew[i] = { icon: 'icon-checkmark', longtext: this.whatsNewData[i] }
- }
- if (this.changelogURL) {
- whatsNew.push({
- href: this.changelogURL,
- text: t('updatenotification', 'View changelog'),
- icon: 'icon-link',
- target: '_blank',
- action: '',
- })
- }
- return whatsNew
- },
-
channelList() {
const channelList = []
@@ -277,6 +293,7 @@ export default {
text: this.currentChannel,
icon: 'icon-rename',
active: true,
+ action: () => {},
})
}
@@ -431,24 +448,12 @@ export default {
this.openedUpdateChannelMenu = false
},
- toggleUpdateChannelMenu() {
- this.openedUpdateChannelMenu = !this.openedUpdateChannelMenu
- },
toggleHideMissingUpdates() {
this.hideMissingUpdates = !this.hideMissingUpdates
},
toggleHideAvailableUpdates() {
this.hideAvailableUpdates = !this.hideAvailableUpdates
},
- toggleMenu() {
- this.openedWhatsNew = !this.openedWhatsNew
- },
- closeUpdateChannelMenu() {
- this.openedUpdateChannelMenu = false
- },
- hideMenu() {
- this.openedWhatsNew = false
- },
},
}
</script>
@@ -459,6 +464,10 @@ export default {
max-width: 900px;
}
+ .topMargin {
+ margin-top: 15px;
+ }
+
div.update,
p:not(.inlineblock) {
margin-bottom: 25px;
@@ -467,17 +476,23 @@ export default {
margin-top: 25px;
}
h3 {
- cursor: pointer;
- .icon {
+ &.clickable {
cursor: pointer;
+ .icon {
+ cursor: pointer;
+ }
}
&:first-of-type {
margin-top: 0;
}
- &.update-channel-selector {
- display: inline-block;
- cursor: inherit;
- }
+ }
+ h4 {
+ margin-block-end: 0.7rem;
+ }
+ .update-channel-selector {
+ display: flex;
+ align-items: center;
+ gap: 12px;
}
.icon {
display: inline-block;
@@ -486,20 +501,6 @@ export default {
.icon-triangle-s, .icon-triangle-n {
opacity: 0.5;
}
- .whatsNew {
- display: inline-block;
- }
- .toggleWhatsNew {
- position: relative;
- }
- .popovermenu {
- p {
- margin-bottom: 0;
- width: 100%;
- }
- margin-top: 5px;
- width: 300px;
- }
.applist {
margin-bottom: 25px;
}
@@ -518,32 +519,33 @@ export default {
opacity: 1;
}
}
- .popovermenu {
- display: none;
- top: 28px;
- &.show-menu {
- display: block;
- }
- }
}
}
</style>
<style lang="scss">
- /* override needed to make menu wider */
- #updatenotification .popovermenu {
- p {
- margin-top: 5px;
- width: 100%;
- }
- margin-top: 5px;
- width: 300px;
+// Make current selected update channel visually visible, remove if NcActionRadio is used
+.update-channel-action[aria-checked=true] {
+ border-inline-start: 4px solid var(--color-primary-element);
+
+ &:hover, &:focus-within {
+ background-color: var(--color-primary-element-light-hover);
}
+
+ button {
+ background-color: var(--color-primary-element-light);
+ color: var(--color-primary-element-light-text);
+ }
+}
+// Ensure outline for focus-visible works even with background color of selected channel
+.update-channel-action[aria-checked] {
+ margin-block: 2px;
+}
+
+#updatenotification {
/* override needed to replace yellow hover state with a dark one */
- #updatenotification .update-menu .icon-star:hover,
- #updatenotification .update-menu .icon-star:focus {
+ .update-menu .icon-star:hover,
+ .update-menu .icon-star:focus {
background-image: var(--icon-starred);
}
- #updatenotification .topMargin {
- margin-top: 15px;
- }
+}
</style>
diff --git a/apps/updatenotification/src/init.js b/apps/updatenotification/src/init.js
index dc25ca363b4..8d0e18e1bf1 100644
--- a/apps/updatenotification/src/init.js
+++ b/apps/updatenotification/src/init.js
@@ -20,6 +20,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
+import { translate, translatePlural } from '@nextcloud/l10n'
import Vue from 'vue'
import Root from './components/UpdateNotification.vue'
@@ -27,10 +28,10 @@ import Root from './components/UpdateNotification.vue'
Vue.mixin({
methods: {
t(app, text, vars, count, options) {
- return OC.L10N.translate(app, text, vars, count, options)
+ return translate(app, text, vars, count, options)
},
n(app, textSingular, textPlural, count, vars, options) {
- return OC.L10N.translatePlural(app, textSingular, textPlural, count, vars, options)
+ return translatePlural(app, textSingular, textPlural, count, vars, options)
},
},
})
diff --git a/apps/user_ldap/l10n/gl.js b/apps/user_ldap/l10n/gl.js
index 0c2b7907ee1..bc7a4a80f89 100644
--- a/apps/user_ldap/l10n/gl.js
+++ b/apps/user_ldap/l10n/gl.js
@@ -65,7 +65,7 @@ OC.L10N.register(
"> 1000 groups found" : "> 1000 grupos atopados",
"> 1000 users found" : "> 1000 usuarios atopados",
"_%n user found_::_%n users found_" : ["%n usuario atopado","%n usuarios atopados"],
- "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Non foi posíbel detectar o atributo nome de usuario que amosar. Especifíqueo vostede mesmo nos axustes avanzados de LDAP. ",
+ "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Non foi posíbel detectar o atributo nome de usuario que amosar. Especifíqueo Vde. mesmo nos axustes avanzados de LDAP. ",
"Could not find the desired feature" : "Non foi posíbel atopar a función desexada",
"Invalid Host" : "Máquina incorrecta",
"LDAP user and group backend" : "Infraestrutura de usuarios e grupos LDAP",
@@ -150,7 +150,7 @@ OC.L10N.register(
"User Display Name Field" : "Campo de nome de usuario para amosar",
"The LDAP attribute to use to generate the user's display name." : "O atributo LDAP a empregar para xerar o nome de usuario para amosar.",
"2nd User Display Name Field" : "2º campo de nome de usuario para amosar",
- "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Un atributo LDAP para ser engadido no nome para amosar entre parénteses. Resulta en p.ex. «Xan Carallás (xan.carallas@exemple.org)».",
+ "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Un atributo LDAP para ser engadido no nome para amosar entre parénteses. Resulta en p.ex. «Xan Carallás (xan.carallas@exemplo.org)».",
"Base User Tree" : "Base da árbore de usuarios",
"One User Base DN per line" : "Un DN base de usuario por liña",
"User Search Attributes" : "Atributos de busca do usuario",
@@ -208,7 +208,7 @@ OC.L10N.register(
"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "De xeito predeterminado, o nome de usuario interno crearase a partir do atributo UUID. Isto asegura que o nome de usuario é único e que non é necesario converter os caracteres. O nome de usuario interno ten a restrición de que só se permiten estes caracteres: [a-zA-Z0-9_.@-]. Outros caracteres substitúense pola súa correspondencia ASCII ou simplemente omítense. Nos casos de colisións engadirase/aumentarase un número. O nome de usuario interno úsase para identificar un usuario internamente. Tamén é o nome predeterminado para o cartafol de inicio do usuario. Tamén forma parte dos URL remotos, por exemplo para todos os servizos DAV. Con esta configuración, pódese anular o comportamento predeterminado. Os cambios só terán efecto nos usuarios LDAP recén asignados (engadidos). Déixeo baleiro para o comportamento predeterminado.",
"Internal Username Attribute:" : "Atributo do nome interno de usuario:",
"Override UUID detection" : "Ignorar a detección do UUID",
- "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Por omisión, o atributo UUID é detectado automaticamente. O atributo UUID utilizase para identificar, sen dúbida, aos usuarios e grupos LDAP. Ademais, crearase o nome interno de usuario baseado no UUID, se non se especifica anteriormente o contrario. Pode anular a configuración e pasar un atributo da súa escolla. Vostede debe asegurarse de que o atributo da súa escolla pode ser recuperado polos usuarios e grupos e de que é único. Déixeo baleiro para o comportamento predeterminado. Os cambios terán efecto só nas novas asignacións (engadidos) de usuarios de LDAP.",
+ "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Por omisión, o atributo UUID é detectado automaticamente. O atributo UUID utilizase para identificar, sen dúbida, aos usuarios e grupos LDAP. Ademais, crearase o nome interno de usuario baseado no UUID, se non se especifica anteriormente o contrario. Pode anular a configuración e pasar un atributo da súa escolla. Vde. debe asegurarse de que o atributo da súa escolla pode ser recuperado polos usuarios e grupos e de que é único. Déixeo baleiro para o comportamento predeterminado. Os cambios terán efecto só nas novas asignacións (engadidos) de usuarios de LDAP.",
"UUID Attribute for Users:" : "Atributo do UUID para usuarios:",
"UUID Attribute for Groups:" : "Atributo do UUID para grupos:",
"Username-LDAP User Mapping" : "Asignación do usuario ao «nome de usuario LDAP»",
diff --git a/apps/user_ldap/l10n/gl.json b/apps/user_ldap/l10n/gl.json
index 078610eb33c..6c18b4670d0 100644
--- a/apps/user_ldap/l10n/gl.json
+++ b/apps/user_ldap/l10n/gl.json
@@ -63,7 +63,7 @@
"> 1000 groups found" : "> 1000 grupos atopados",
"> 1000 users found" : "> 1000 usuarios atopados",
"_%n user found_::_%n users found_" : ["%n usuario atopado","%n usuarios atopados"],
- "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Non foi posíbel detectar o atributo nome de usuario que amosar. Especifíqueo vostede mesmo nos axustes avanzados de LDAP. ",
+ "Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings." : "Non foi posíbel detectar o atributo nome de usuario que amosar. Especifíqueo Vde. mesmo nos axustes avanzados de LDAP. ",
"Could not find the desired feature" : "Non foi posíbel atopar a función desexada",
"Invalid Host" : "Máquina incorrecta",
"LDAP user and group backend" : "Infraestrutura de usuarios e grupos LDAP",
@@ -148,7 +148,7 @@
"User Display Name Field" : "Campo de nome de usuario para amosar",
"The LDAP attribute to use to generate the user's display name." : "O atributo LDAP a empregar para xerar o nome de usuario para amosar.",
"2nd User Display Name Field" : "2º campo de nome de usuario para amosar",
- "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Un atributo LDAP para ser engadido no nome para amosar entre parénteses. Resulta en p.ex. «Xan Carallás (xan.carallas@exemple.org)».",
+ "Optional. An LDAP attribute to be added to the display name in brackets. Results in e.g. »John Doe (john.doe@example.org)«." : "Opcional. Un atributo LDAP para ser engadido no nome para amosar entre parénteses. Resulta en p.ex. «Xan Carallás (xan.carallas@exemplo.org)».",
"Base User Tree" : "Base da árbore de usuarios",
"One User Base DN per line" : "Un DN base de usuario por liña",
"User Search Attributes" : "Atributos de busca do usuario",
@@ -206,7 +206,7 @@
"By default the internal username will be created from the UUID attribute. It makes sure that the username is unique and characters do not need to be converted. The internal username has the restriction that only these characters are allowed: [a-zA-Z0-9_.@-]. Other characters are replaced with their ASCII correspondence or simply omitted. On collisions a number will be added/increased. The internal username is used to identify a user internally. It is also the default name for the user home folder. It is also a part of remote URLs, for instance for all DAV services. With this setting, the default behavior can be overridden. Changes will have effect only on newly mapped (added) LDAP users. Leave it empty for default behavior." : "De xeito predeterminado, o nome de usuario interno crearase a partir do atributo UUID. Isto asegura que o nome de usuario é único e que non é necesario converter os caracteres. O nome de usuario interno ten a restrición de que só se permiten estes caracteres: [a-zA-Z0-9_.@-]. Outros caracteres substitúense pola súa correspondencia ASCII ou simplemente omítense. Nos casos de colisións engadirase/aumentarase un número. O nome de usuario interno úsase para identificar un usuario internamente. Tamén é o nome predeterminado para o cartafol de inicio do usuario. Tamén forma parte dos URL remotos, por exemplo para todos os servizos DAV. Con esta configuración, pódese anular o comportamento predeterminado. Os cambios só terán efecto nos usuarios LDAP recén asignados (engadidos). Déixeo baleiro para o comportamento predeterminado.",
"Internal Username Attribute:" : "Atributo do nome interno de usuario:",
"Override UUID detection" : "Ignorar a detección do UUID",
- "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Por omisión, o atributo UUID é detectado automaticamente. O atributo UUID utilizase para identificar, sen dúbida, aos usuarios e grupos LDAP. Ademais, crearase o nome interno de usuario baseado no UUID, se non se especifica anteriormente o contrario. Pode anular a configuración e pasar un atributo da súa escolla. Vostede debe asegurarse de que o atributo da súa escolla pode ser recuperado polos usuarios e grupos e de que é único. Déixeo baleiro para o comportamento predeterminado. Os cambios terán efecto só nas novas asignacións (engadidos) de usuarios de LDAP.",
+ "By default, the UUID attribute is automatically detected. The UUID attribute is used to doubtlessly identify LDAP users and groups. Also, the internal username will be created based on the UUID, if not specified otherwise above. You can override the setting and pass an attribute of your choice. You must make sure that the attribute of your choice can be fetched for both users and groups and it is unique. Leave it empty for default behavior. Changes will have effect only on newly mapped (added) LDAP users and groups." : "Por omisión, o atributo UUID é detectado automaticamente. O atributo UUID utilizase para identificar, sen dúbida, aos usuarios e grupos LDAP. Ademais, crearase o nome interno de usuario baseado no UUID, se non se especifica anteriormente o contrario. Pode anular a configuración e pasar un atributo da súa escolla. Vde. debe asegurarse de que o atributo da súa escolla pode ser recuperado polos usuarios e grupos e de que é único. Déixeo baleiro para o comportamento predeterminado. Os cambios terán efecto só nas novas asignacións (engadidos) de usuarios de LDAP.",
"UUID Attribute for Users:" : "Atributo do UUID para usuarios:",
"UUID Attribute for Groups:" : "Atributo do UUID para grupos:",
"Username-LDAP User Mapping" : "Asignación do usuario ao «nome de usuario LDAP»",
diff --git a/apps/workflowengine/l10n/de.js b/apps/workflowengine/l10n/de.js
index 06ce3246c3b..2fd7794d476 100644
--- a/apps/workflowengine/l10n/de.js
+++ b/apps/workflowengine/l10n/de.js
@@ -48,6 +48,7 @@ OC.L10N.register(
"Nextcloud workflow engine" : "Nextcloud Arbeitsablauf-Engine",
"Select a filter" : "Filter auswählen",
"Select a comparator" : "Wähle einen Komparator",
+ "Remove filter" : "Filter entfernen",
"Select a file type" : "Dateityp auswählen",
"e.g. httpd/unix-directory" : "z. B. httpd/unix-directory",
"Folder" : "Ordner",
@@ -66,10 +67,11 @@ OC.L10N.register(
"Desktop client" : "Desktop-Client",
"Thunderbird & Outlook addons" : "Thunderbird & Outlook Add-ons",
"Custom user agent" : "Benutzerdefinierter User-Agent",
- "At least one event must be selected" : "Mindestens ein Termin muss ausgewählt werden",
+ "At least one event must be selected" : "Mindestens ein Ereignis muss ausgewählt werden",
"Add new flow" : "Neuen Ablauf hinzufügen",
"When" : "Wenn",
"and" : "und",
+ "Add a new filter" : "Neuen Filter hinzufügen",
"Cancel" : "Abbrechen",
"Delete" : "Löschen",
"The configuration is invalid" : "Die Konfiguration ist ungültig",
diff --git a/apps/workflowengine/l10n/de.json b/apps/workflowengine/l10n/de.json
index 90f020765d4..e45b6b02632 100644
--- a/apps/workflowengine/l10n/de.json
+++ b/apps/workflowengine/l10n/de.json
@@ -46,6 +46,7 @@
"Nextcloud workflow engine" : "Nextcloud Arbeitsablauf-Engine",
"Select a filter" : "Filter auswählen",
"Select a comparator" : "Wähle einen Komparator",
+ "Remove filter" : "Filter entfernen",
"Select a file type" : "Dateityp auswählen",
"e.g. httpd/unix-directory" : "z. B. httpd/unix-directory",
"Folder" : "Ordner",
@@ -64,10 +65,11 @@
"Desktop client" : "Desktop-Client",
"Thunderbird & Outlook addons" : "Thunderbird & Outlook Add-ons",
"Custom user agent" : "Benutzerdefinierter User-Agent",
- "At least one event must be selected" : "Mindestens ein Termin muss ausgewählt werden",
+ "At least one event must be selected" : "Mindestens ein Ereignis muss ausgewählt werden",
"Add new flow" : "Neuen Ablauf hinzufügen",
"When" : "Wenn",
"and" : "und",
+ "Add a new filter" : "Neuen Filter hinzufügen",
"Cancel" : "Abbrechen",
"Delete" : "Löschen",
"The configuration is invalid" : "Die Konfiguration ist ungültig",
diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php
index 991d7ebc739..dfcfed9466b 100644
--- a/apps/workflowengine/lib/Check/FileMimeType.php
+++ b/apps/workflowengine/lib/Check/FileMimeType.php
@@ -107,13 +107,7 @@ class FileMimeType extends AbstractStringCheck implements IFileCheck {
* @return bool
*/
public function executeCheck($operator, $value) {
- $actualValue = $this->getActualValue();
- $plainMimetypeResult = $this->executeStringCheck($operator, $value, $actualValue);
- if ($actualValue === 'httpd/unix-directory') {
- return $plainMimetypeResult;
- }
- $detectMimetypeBasedOnFilenameResult = $this->executeStringCheck($operator, $value, $this->mimeTypeDetector->detectPath($this->path));
- return $plainMimetypeResult || $detectMimetypeBasedOnFilenameResult;
+ return $this->executeStringCheck($operator, $value, $this->getActualValue());
}
/**
diff --git a/apps/workflowengine/src/components/Event.vue b/apps/workflowengine/src/components/Event.vue
index b2a68c16dbd..c9b93f3f097 100644
--- a/apps/workflowengine/src/components/Event.vue
+++ b/apps/workflowengine/src/components/Event.vue
@@ -4,36 +4,35 @@
<img class="option__icon" :src="entity.icon" alt="">
<span class="option__title option__title_single">{{ operation.triggerHint }}</span>
</div>
- <NcMultiselect v-else
- :value="currentEvent"
- :options="allEvents"
- track-by="id"
- :multiple="true"
- :auto-limit="false"
+ <NcSelect v-else
:disabled="allEvents.length <= 1"
+ :multiple="true"
+ :options="allEvents"
+ :value="currentEvent"
+ :placeholder="placeholderString"
+ class="event__trigger"
+ label="displayName"
@input="updateEvent">
- <template slot="selection" slot-scope="{ values, isOpen }">
- <div v-if="values.length && !isOpen" class="eventlist">
- <img class="option__icon" :src="values[0].entity.icon" alt="">
- <span v-for="(value, index) in values" :key="value.id" class="text option__title option__title_single">{{ value.displayName }} <span v-if="index+1 < values.length">, </span></span>
- </div>
+ <template #option="option">
+ <img class="option__icon" :src="option.entity.icon" alt="">
+ <span class="option__title">{{ option.displayName }}</span>
</template>
- <template slot="option" slot-scope="props">
- <img class="option__icon" :src="props.option.entity.icon" alt="">
- <span class="option__title">{{ props.option.displayName }}</span>
+ <template #selected-option="option">
+ <img class="option__icon" :src="option.entity.icon" alt="">
+ <span class="option__title">{{ option.displayName }}</span>
</template>
- </NcMultiselect>
+ </NcSelect>
</div>
</template>
<script>
-import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js'
+import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import { showWarning } from '@nextcloud/dialogs'
export default {
name: 'Event',
components: {
- NcMultiselect,
+ NcSelect,
},
props: {
rule: {
@@ -54,10 +53,15 @@ export default {
currentEvent() {
return this.allEvents.filter(event => event.entity.id === this.rule.entity && this.rule.events.indexOf(event.eventName) !== -1)
},
+ placeholderString() {
+ // TRANSLATORS: Users should select a trigger for a workflow action
+ return t('workflowengine', 'Select a trigger')
+ },
},
methods: {
updateEvent(events) {
if (events.length === 0) {
+ // TRANSLATORS: Users must select an event as of "happening" or "incident" which triggers an action
showWarning(t('workflowengine', 'At least one event must be selected'))
return
}
@@ -81,6 +85,10 @@ export default {
<style scoped lang="scss">
.event {
margin-bottom: 5px;
+
+ &__trigger {
+ max-width: 550px;
+ }
}
.isComplex {
img {
@@ -91,51 +99,15 @@ export default {
display: inline-block;
}
}
- .multiselect {
- width: 100%;
- max-width: 550px;
- margin-top: 4px;
- }
- .multiselect::v-deep .multiselect__single {
- display: flex;
- }
- .multiselect:not(.multiselect--active)::v-deep .multiselect__tags {
- background-color: var(--color-main-background) !important;
- border: 1px solid transparent;
- }
-
- .multiselect::v-deep .multiselect__tags {
- background-color: var(--color-main-background) !important;
- height: auto;
- min-height: 34px;
- }
-
- .multiselect:not(.multiselect--disabled)::v-deep .multiselect__tags .multiselect__single {
- background-image: var(--icon-triangle-s-dark);
- background-repeat: no-repeat;
- background-position: right center;
- }
-
- input {
- border: 1px solid transparent;
- }
.option__title {
margin-left: 5px;
color: var(--color-main-text);
}
- .option__title_single {
- font-weight: 900;
- }
.option__icon {
width: 16px;
height: 16px;
filter: var(--background-invert-if-dark);
}
-
- .eventlist img,
- .eventlist .text {
- vertical-align: middle;
- }
</style>
diff --git a/apps/workflowengine/tests/Check/FileMimeTypeTest.php b/apps/workflowengine/tests/Check/FileMimeTypeTest.php
index 3ebcaa8f4b3..e5a4c99269f 100644
--- a/apps/workflowengine/tests/Check/FileMimeTypeTest.php
+++ b/apps/workflowengine/tests/Check/FileMimeTypeTest.php
@@ -127,7 +127,7 @@ class FileMimeTypeTest extends TestCase {
$check = new FileMimeType($this->l10n, $this->request, $this->mimeDetector);
$check->setFileInfo($storage, 'foo/bar.txt');
- $this->assertTrue($check->executeCheck('is', 'text/plain-path-detected'));
+ $this->assertTrue($check->executeCheck('is', 'text/plain-content-detected'));
}
public function testFallback() {
@@ -142,7 +142,7 @@ class FileMimeTypeTest extends TestCase {
public function testFromCacheCached() {
$storage = new Temporary([]);
$storage->mkdir('foo');
- $storage->file_put_contents('foo/bar.txt', 'asd');
+ $storage->file_put_contents('foo/bar.txt', 'text-content');
$storage->getScanner()->scan('');
$check = new FileMimeType($this->l10n, $this->request, $this->mimeDetector);
@@ -156,7 +156,7 @@ class FileMimeTypeTest extends TestCase {
$newCheck = new FileMimeType($this->l10n, $this->request, $this->mimeDetector);
$newCheck->setFileInfo($storage, 'foo/bar.txt');
- $this->assertTrue($newCheck->executeCheck('is', 'text/plain-path-detected'));
+ $this->assertTrue($newCheck->executeCheck('is', 'text/plain-content-detected'));
}
public function testExistsCached() {