diff --git a/.drone.env b/.drone.env index be1d6c65bd7..b03d78a8422 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The version of OCIS to use in pipelines that test against OCIS -OCIS_COMMITID=7bda03c386cfaded7e5de6a48088c4ec4e02bccd +OCIS_COMMITID=43ee37d02ed652b89e88c03c6bbeb5c89a62ce16 OCIS_BRANCH=master diff --git a/.drone.star b/.drone.star index f0da71b49b7..e310ba19644 100644 --- a/.drone.star +++ b/.drone.star @@ -6,23 +6,20 @@ NOTIFICATIONS = 3 ALPINE_GIT = "alpine/git:latest" APACHE_TIKA = "apache/tika:2.8.0.0" DEEPDIVER_DOCKER_ORACLE_XE_11G = "deepdiver/docker-oracle-xe-11g:latest" -DRONE_CLI_ALPINE = "drone/cli:alpine" MINIO_MC = "minio/mc:RELEASE.2021-10-07T04-19-58Z" OC_CI_ALPINE = "owncloudci/alpine:latest" OC_CI_BAZEL_BUILDIFIER = "owncloudci/bazel-buildifier" OC_CI_CORE_NODEJS = "owncloudci/core:nodejs14" OC_CI_DRONE_ANSIBLE = "owncloudci/drone-ansible:latest" -OC_CI_DRONE_CANCEL_PREVIOUS_BUILDS = "owncloudci/drone-cancel-previous-builds" OC_CI_DRONE_SKIP_PIPELINE = "owncloudci/drone-skip-pipeline" OC_CI_GOLANG = "owncloudci/golang:1.19" -OC_CI_HUGO = "owncloudci/hugo:0.89.4" +OC_CI_HUGO = "owncloudci/hugo:0.115.2" OC_CI_NODEJS = "owncloudci/nodejs:18" OC_CI_PHP = "owncloudci/php:7.4" OC_CI_WAIT_FOR = "owncloudci/wait-for:latest" OC_TESTING_MIDDLEWARE = "owncloud/owncloud-test-middleware:1.8.3" OC_UBUNTU = "owncloud/ubuntu:20.04" PLUGINS_DOCKER = "plugins/docker:20.14" -PLUGINS_DOWNSTREAM = "plugins/downstream" PLUGINS_GH_PAGES = "plugins/gh-pages:1" PLUGINS_GIT_ACTION = "plugins/git-action:1" PLUGINS_GITHUB_RELEASE = "plugins/github-release:1" @@ -32,7 +29,6 @@ PLUGINS_SLACK = "plugins/slack:1" SELENIUM_STANDALONE_CHROME = "selenium/standalone-chrome:104.0-20220812" SELENIUM_STANDALONE_FIREFOX = "selenium/standalone-firefox:104.0-20220812" SONARSOURCE_SONAR_SCANNER_CLI = "sonarsource/sonar-scanner-cli:4.7.0" -THEGEEKLAB_DRONE_GITHUB_COMMENT = "thegeeklab/drone-github-comment:1" TOOLHIPPIE_CALENS = "toolhippie/calens:latest" OC10_VERSION = "latest" @@ -64,7 +60,7 @@ config = { "app": "web", "rocketchat": { "channel": "builds", - "from_secret": "private_rocketchat", + "from_secret": "rocketchat_chat_webhook", }, "branches": [ "master", @@ -667,10 +663,10 @@ ocisSpecificTestSuites = [ # minio mc environment variables minio_mc_environment = { "CACHE_BUCKET": { - "from_secret": "cache_public_s3_bucket", + "from_secret": "cache_s3_bucket", }, "MC_HOST": { - "from_secret": "cache_s3_endpoint", + "from_secret": "cache_s3_server", }, "AWS_ACCESS_KEY_ID": { "from_secret": "cache_s3_access_key", @@ -763,8 +759,7 @@ def main(ctx): return pipelines def beforePipelines(ctx): - return cancelPreviousBuilds() + \ - checkStarlark() + \ + return checkStarlark() + \ licenseCheck(ctx) + \ documentation(ctx) + \ changelog(ctx) + \ @@ -844,30 +839,6 @@ def pnpmlint(ctx): return pipelines -def cancelPreviousBuilds(): - return [{ - "kind": "pipeline", - "type": "docker", - "name": "cancel-previous-builds", - "clone": { - "disable": True, - }, - "steps": [{ - "name": "cancel-previous-builds", - "image": OC_CI_DRONE_CANCEL_PREVIOUS_BUILDS, - "settings": { - "DRONE_TOKEN": { - "from_secret": "drone_token", - }, - }, - }], - "trigger": { - "ref": [ - "refs/pull/**", - ], - }, - }] - def build(ctx): pipelines = [] @@ -1124,7 +1095,6 @@ def e2eTests(ctx): default = { "skip": False, - "earlyFail": True, "logLevel": "2", "reportTracing": "false", "db": "mysql:5.5", @@ -1151,9 +1121,6 @@ def e2eTests(ctx): if params["skip"]: continue - if ("full-ci" in ctx.build.title.lower()): - params["earlyFail"] = False - if ("with-tracing" in ctx.build.title.lower()): params["reportTracing"] = "true" @@ -1212,12 +1179,7 @@ def e2eTests(ctx): ], }] + \ uploadTracingResult(ctx) + \ - publishTracingResult(ctx, "e2e-tests %s" % server) - if (params["earlyFail"]): - steps += buildGithubCommentForBuildStopped("e2e-ocis" if server.startswith("oCIS") else "e2e-oc10") - steps += githubComment("e2e-tests", server) - if (params["earlyFail"]): - steps += stopBuild() + logTracingResult(ctx, "e2e-tests %s" % server) pipelines.append({ "kind": "pipeline", @@ -1261,7 +1223,6 @@ def acceptance(ctx): "oc10IntegrationAppIncluded": False, "skip": False, "debugSuites": [], - "earlyFail": True, "retry": True, } @@ -1300,9 +1261,6 @@ def acceptance(ctx): for item in default: params[item] = matrix[item] if item in matrix else default[item] - if ("full-ci" in ctx.build.title.lower()): - params["earlyFail"] = False - for server in params["servers"]: for browser in params["browsers"]: for db in params["databases"]: @@ -1393,17 +1351,7 @@ def acceptance(ctx): # Capture the screenshots from acceptance tests (only runs on failure) if (params["screenShots"]): - steps += uploadScreenshots() + buildGithubComment(suiteName) - - if (params["earlyFail"]): - steps += buildGithubCommentForBuildStopped(suiteName) - - # Upload the screenshots to github comment - server_type = "oCIS" if params["runningOnOCIS"] else "oC10" - steps += githubComment("acceptance", server_type) - - if (params["earlyFail"]): - steps += stopBuild() + steps += uploadScreenshots() + logAcceptanceTestsScreenshotsResult(suiteName) result = { "kind": "pipeline", @@ -1958,26 +1906,6 @@ def documentation(ctx): }, }, }, - { - "name": "downstream", - "image": PLUGINS_DOWNSTREAM, - "settings": { - "server": "https://drone.owncloud.com/", - "token": { - "from_secret": "drone_token", - }, - "repositories": [ - "owncloud/owncloud.github.io@main", - ], - }, - "when": { - "ref": { - "exclude": [ - "refs/pull/**", - ], - }, - }, - }, ], "trigger": { "ref": [ @@ -2189,6 +2117,7 @@ def ocisService(type): environment["FRONTEND_FULL_TEXT_SEARCH_ENABLED"] = True environment["SEARCH_EXTRACTOR_TYPE"] = "tika" environment["SEARCH_EXTRACTOR_TIKA_TIKA_URL"] = "http://tika:9998" + environment["SEARCH_EXTRACTOR_CS3SOURCE_INSECURE"] = True return [ { @@ -2303,6 +2232,7 @@ def setupNotificationsAppForServer(): "rm -rf %s/apps/notifications" % dir["server"], "git clone -b master https://github.com/owncloud/notifications.git %s/apps/notifications" % dir["server"], "cd %s || exit" % dir["server"], + "php occ upgrade", "php occ a:e notifications", "php occ a:l", ], @@ -2534,29 +2464,6 @@ def cacheOcis(): ], }] -def stopBuild(): - return [{ - "name": "stop-build", - "image": DRONE_CLI_ALPINE, - "environment": { - "DRONE_SERVER": "https://drone.owncloud.com", - "DRONE_TOKEN": { - "from_secret": "drone_token", - }, - }, - "commands": [ - "drone build stop owncloud/web ${DRONE_BUILD_NUMBER}", - ], - "when": { - "status": [ - "failure", - ], - "event": [ - "pull_request", - ], - }, - }] - def uploadScreenshots(): return [{ "name": "upload-screenshots", @@ -2592,80 +2499,6 @@ def uploadScreenshots(): }, }] -def buildGithubComment(suite): - return [{ - "name": "build-github-comment", - "image": OC_UBUNTU, - "commands": [ - "cd %s/tests/acceptance/reports/screenshots/" % dir["web"], - 'echo "
:boom: The acceptance tests failed on retry. Please find the screenshots inside ...\\n\\n

\\n\\n" >> %s/comments.file' % dir["web"], - 'for f in *.png; do echo "### $f\n" \'!\'"[$f]($CACHE_ENDPOINT/$CACHE_BUCKET/${DRONE_REPO}/${DRONE_BUILD_NUMBER}/screenshots/$f) \n" >> %s/comments.file; done' % dir["web"], - 'echo "\n

" >> %s/comments.file' % dir["web"], - "more %s/comments.file" % dir["web"], - ], - "environment": { - "TEST_CONTEXT": suite, - "CACHE_ENDPOINT": { - "from_secret": "cache_public_s3_server", - }, - "CACHE_BUCKET": { - "from_secret": "cache_public_s3_bucket", - }, - }, - "when": { - "status": [ - "failure", - ], - "event": [ - "pull_request", - ], - }, - }] - -def buildGithubCommentForBuildStopped(suite): - return [{ - "name": "build-github-comment-buildStop", - "image": OC_UBUNTU, - "commands": [ - 'echo ":boom: The %s tests pipeline failed. The build has been cancelled.\\n" >> %s/comments.file' % (suite, dir["web"]), - ], - "when": { - "status": [ - "failure", - ], - "event": [ - "pull_request", - ], - }, - }] - -def githubComment(alternateSuiteName, server_type = ""): - prefix = "Results for %s %s ${DRONE_BUILD_LINK}/${DRONE_JOB_NUMBER}${DRONE_STAGE_NUMBER}/1" % (alternateSuiteName, server_type) - return [{ - "name": "github-comment", - "image": THEGEEKLAB_DRONE_GITHUB_COMMENT, - "pull": "if-not-exists", - "settings": { - "message": "%s/comments.file" % dir["web"], - "key": "pr-${DRONE_PULL_REQUEST}-%s" % server_type, #TODO: we could delete the comment after a successful CI run - "update": "true", - "api_key": { - "from_secret": "github_token", - }, - }, - "commands": [ - "if [ -s %s ]; then echo '%s' | cat - comments.file > temp && mv temp comments.file && /bin/drone-github-comment; fi" % (dir["commentsFile"], prefix), - ], - "when": { - "status": [ - "failure", - ], - "event": [ - "pull_request", - ], - }, - }] - def example_deploys(ctx): on_merge_deploy = [ "ocis_web/latest.yml", @@ -2967,7 +2800,7 @@ def genericCache(name, action, mounts, cache_path): "image": PLUGINS_S3_CACHE, "settings": { "endpoint": { - "from_secret": "cache_s3_endpoint", + "from_secret": "cache_s3_server", }, "rebuild": rebuild, "restore": restore, @@ -3003,7 +2836,7 @@ def genericCachePurge(flush_path): "from_secret": "cache_s3_access_key", }, "endpoint": { - "from_secret": "cache_s3_endpoint", + "from_secret": "cache_s3_server", }, "secret_key": { "from_secret": "cache_s3_secret_key", @@ -3115,6 +2948,25 @@ def pipelineSanityChecks(ctx, pipelines): for image in images.keys(): print(" %sx\t%s" % (images[image], image)) +def logAcceptanceTestsScreenshotsResult(suite): + return [{ + "name": "log-acceptance-tests-screenshot", + "image": OC_UBUNTU, + "commands": [ + "cd %s/tests/acceptance/reports/screenshots/" % dir["web"], + 'echo "To see the screenshots, please visit the following path"', + 'for f in *.png; do echo "### $f\n" \'!\'"(https://cache.owncloud.com/public/${DRONE_REPO}/${DRONE_BUILD_NUMBER}/screenshots/$f) \n"; done', + ], + "when": { + "status": [ + "failure", + ], + "event": [ + "pull_request", + ], + }, + }] + def uploadTracingResult(ctx): status = ["failure"] if ("with-tracing" in ctx.build.title.lower()): @@ -3153,30 +3005,20 @@ def uploadTracingResult(ctx): }, }] -def publishTracingResult(ctx, suite): +def logTracingResult(ctx, suite): status = ["failure"] + if ("with-tracing" in ctx.build.title.lower()): status = ["failure", "success"] return [{ - "name": "publish-tracing-result", + "name": "log-tracing-result", "image": OC_UBUNTU, "commands": [ "cd %s/reports/e2e/playwright/tracing/" % dir["web"], - 'echo "
:boom: To see the trace, please open the link in the console ...\\n\\n

\\n\\n" >> %s/comments.file' % dir["web"], - 'for f in *.zip; do echo "#### npx playwright show-trace $CACHE_ENDPOINT/$CACHE_BUCKET/${DRONE_REPO}/${DRONE_BUILD_NUMBER}/tracing/$f \n" >> %s/comments.file; done' % dir["web"], - 'echo "\n

" >> %s/comments.file' % dir["web"], - "more %s/comments.file" % dir["web"], + 'echo "To see the trace, please open the following link in the console"', + 'for f in *.zip; do echo "npx playwright show-trace https://cache.owncloud.com/public/${DRONE_REPO}/${DRONE_BUILD_NUMBER}/tracing/$f \n"; done', ], - "environment": { - "TEST_CONTEXT": suite, - "CACHE_ENDPOINT": { - "from_secret": "cache_public_s3_server", - }, - "CACHE_BUCKET": { - "from_secret": "cache_public_s3_bucket", - }, - }, "when": { "status": status, "event": [ diff --git a/.gitignore b/.gitignore index 6e92a4de5fa..979471aba06 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/version.json /hugo +.env .idea .vscode *.bundle.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 1292e7378bc..790761d00af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,17 @@ Summary * Bugfix - Favorites list update on removal: [#9078](https://github.com/owncloud/web/pull/9078) * Bugfix - Space creation does not block reoccurring event: [#9213](https://github.com/owncloud/web/pull/9213) +* Bugfix - Uploading to folders that contain special characters: [#9247](https://github.com/owncloud/web/issues/9247) +* Bugfix - Relative user quota display limited to two decimals: [#9259](https://github.com/owncloud/web/issues/9259) +* Bugfix - Remember location after token invalidation: [#9261](https://github.com/owncloud/web/issues/9261) +* Bugfix - Authenticated public links breaking uploads: [#9299](https://github.com/owncloud/web/pull/9299) +* Bugfix - Switch columns displayed on small screens in "Shared with me" view: [#9315](https://github.com/owncloud/web/issues/9315) +* Bugfix - Media controls overflow on mobile screens: [#9351](https://github.com/owncloud/web/pull/9351) +* Bugfix - Space editors see empty trashbin and delete actions in space trashbin: [#9389](https://github.com/owncloud/web/pull/9389) +* Bugfix - Merging folders: [#9461](https://github.com/owncloud/web/issues/9461) +* Enhancement - Scroll to newly created folder: [#7600](https://github.com/owncloud/web/issues/7600) * Enhancement - Add hasPriority property for editors per extension: [#7967](https://github.com/owncloud/web/pull/7967) +* Enhancement - Improve extension app topbar: [#8422](https://github.com/owncloud/web/issues/8422) * Enhancement - Open individually shared file in dedicated view: [#8445](https://github.com/owncloud/web/issues/8445) * Enhancement - Shrink table columns: [#8599](https://github.com/owncloud/web/issues/8599) * Enhancement - Add whitespace context-menu: [#8921](https://github.com/owncloud/web/pull/8921) @@ -34,8 +44,20 @@ Summary * Enhancement - Add batch actions to search result list: [#9200](https://github.com/owncloud/web/pull/9200) * Enhancement - Restyle possible sharees: [#9216](https://github.com/owncloud/web/issues/9216) * Enhancement - Streamline URL query names: [#9226](https://github.com/owncloud/web/pull/9226) +* Enhancement - Access denied page update message: [#9263](https://github.com/owncloud/web/pull/9263) * Enhancement - Hover tooltips in topbar: [#9280](https://github.com/owncloud/web/issues/9280) * Enhancement - Search list add highlighted file content: [#9294](https://github.com/owncloud/web/pull/9294) +* Enhancement - Resolve pulic links to their actual location: [#9299](https://github.com/owncloud/web/pull/9299) +* Enhancement - Add search location filter: [#9304](https://github.com/owncloud/web/pull/9304) +* Enhancement - Ambiguation for URL view mode params: [#9344](https://github.com/owncloud/web/pull/9344) +* Enhancement - Batch actions redesign: [#9346](https://github.com/owncloud/web/pull/9346) +* Enhancement - Tag comma separation on client side: [#9348](https://github.com/owncloud/web/pull/9348) +* Enhancement - User notification for blocked pop-ups and redirects: [#9377](https://github.com/owncloud/web/issues/9377) +* Enhancement - Allow local storage for auth token: [#9386](https://github.com/owncloud/web/pull/9386) +* Enhancement - Button styling: [#9394](https://github.com/owncloud/web/pull/9394) +* Enhancement - Show local loading spinner in sharing button: [#9423](https://github.com/owncloud/web/pull/9423) +* Enhancement - Add error log to upload dialog: [#9436](https://github.com/owncloud/web/pull/9436) +* Enhancement - File versions tooltip with absolute date: [#9441](https://github.com/owncloud/web/pull/9441) Details ------- @@ -55,6 +77,78 @@ Details https://github.com/owncloud/web/issues/9189 https://github.com/owncloud/web/pull/9213 +* Bugfix - Uploading to folders that contain special characters: [#9247](https://github.com/owncloud/web/issues/9247) + + Uploading resources to folders that contain special characters in their names has been fixed. + + https://github.com/owncloud/web/issues/9247 + https://github.com/owncloud/web/pull/9290 + +* Bugfix - Relative user quota display limited to two decimals: [#9259](https://github.com/owncloud/web/issues/9259) + + If the relative user quota is being reported too precisely from the backend, there was a chance + of small display issue. This has been resolved by limiting the number of decimals that get + displayed for the relative quota to two. + + https://github.com/owncloud/web/issues/9259 + https://github.com/owncloud/web/pull/9369 + +* Bugfix - Remember location after token invalidation: [#9261](https://github.com/owncloud/web/issues/9261) + + Fixed an issue where token invalidation in the IDP would result in losing the current location. + So logging in after token invalidation now correctly redirects to the page the user was before. + + https://github.com/owncloud/web/issues/9261 + https://github.com/owncloud/web/pull/9364 + +* Bugfix - Authenticated public links breaking uploads: [#9299](https://github.com/owncloud/web/pull/9299) + + Opening public links in an authenticated context no longer breaks uploading resources. + + https://github.com/owncloud/web/issues/9298 + https://github.com/owncloud/web/pull/9299 + +* Bugfix - Switch columns displayed on small screens in "Shared with me" view: [#9315](https://github.com/owncloud/web/issues/9315) + + Visibility of the resource table columns "Shared by" and "Shared with" has been switched for + small screen and tablets (screen size <1200px) in the "Shared with me" view. + + https://github.com/owncloud/web/issues/9315 + https://github.com/owncloud/web/pull/9320 + +* Bugfix - Media controls overflow on mobile screens: [#9351](https://github.com/owncloud/web/pull/9351) + + Media controls overflowed on smaller 9:16 screens because it is absolutely positioned and + centered using transform property, its margin caused the transform operation to not + calculate the center properly (the latter also affected desktop but was merely visible). + + https://github.com/owncloud/web/issues/9318 + https://github.com/owncloud/web/pull/9351 + +* Bugfix - Space editors see empty trashbin and delete actions in space trashbin: [#9389](https://github.com/owncloud/web/pull/9389) + + We've fixed a bug, where space editors were able to see the empty trashbin and delete buttons in + the space's trashbin. This is only allowed for space managers. + + https://github.com/owncloud/web/issues/9385 + https://github.com/owncloud/web/pull/9389 + +* Bugfix - Merging folders: [#9461](https://github.com/owncloud/web/issues/9461) + + Merging folders as option to handle name conflicts has been fixed. + + https://github.com/owncloud/web/issues/9461 + https://github.com/owncloud/web/pull/9477 + +* Enhancement - Scroll to newly created folder: [#7600](https://github.com/owncloud/web/issues/7600) + + After creating a new folder that gets sorted into the currently displayed resources but + outside of the current viewport, we now scroll to the new folder. + + https://github.com/owncloud/web/issues/7600 + https://github.com/owncloud/web/issues/7601 + https://github.com/owncloud/web/pulls/8145 + * Enhancement - Add hasPriority property for editors per extension: [#7967](https://github.com/owncloud/web/pull/7967) HasPriority property has been added to replace canBeDefault. The property allows to set @@ -62,6 +156,14 @@ Details https://github.com/owncloud/web/pull/7967 +* Enhancement - Improve extension app topbar: [#8422](https://github.com/owncloud/web/issues/8422) + + We have redesigned the app topbar used in extensions and added more file actions. + + https://github.com/owncloud/web/issues/8422 + https://github.com/owncloud/web/pull/8442 + https://github.com/owncloud/web/pull/8447 + * Enhancement - Open individually shared file in dedicated view: [#8445](https://github.com/owncloud/web/issues/8445) We have added functionality to open a single, publicly shared file in a different view (instead @@ -118,6 +220,7 @@ Details https://github.com/owncloud/web/issues/9045 https://github.com/owncloud/web/pull/9046 + https://github.com/owncloud/web/pull/9307 * Enhancement - Drag & drop on parent folder: [#9052](https://github.com/owncloud/web/pull/9052) @@ -152,6 +255,7 @@ Details https://github.com/owncloud/web/pull/9059 https://github.com/owncloud/web/pull/9087 https://github.com/owncloud/web/pull/9096 + https://github.com/owncloud/web/pull/9332 * Enhancement - Advanced search button: [#9077](https://github.com/owncloud/web/pull/9077) @@ -190,9 +294,18 @@ Details currently the only supported option. https://github.com/owncloud/web/issues/9151 + https://github.com/owncloud/web/issues/9445 + https://github.com/owncloud/web/issues/9469 + https://github.com/owncloud/web/issues/9454 + https://github.com/owncloud/web/issues/9486 https://github.com/owncloud/web/pull/9150 https://github.com/owncloud/web/pull/9282 https://github.com/owncloud/web/pull/9291 + https://github.com/owncloud/web/pull/9374 + https://github.com/owncloud/web/pull/9460 + https://github.com/owncloud/web/pull/9471 + https://github.com/owncloud/web/pull/9470 + https://github.com/owncloud/web/pull/9487 * Enhancement - Privacy statement in account menu: [#9174](https://github.com/owncloud/web/issues/9174) @@ -250,6 +363,14 @@ Details https://github.com/owncloud/web/pull/9226 +* Enhancement - Access denied page update message: [#9263](https://github.com/owncloud/web/pull/9263) + + We've updated the access denied page message to be more specific and give the user a link to a help + page. + + https://github.com/owncloud/web/issues/9051 + https://github.com/owncloud/web/pull/9263 + * Enhancement - Hover tooltips in topbar: [#9280](https://github.com/owncloud/web/issues/9280) We have added more hoverable tooltips in the topbar to align the behavior. @@ -265,6 +386,102 @@ Details https://github.com/owncloud/web/issues/9295 https://github.com/owncloud/web/pull/9294 +* Enhancement - Resolve pulic links to their actual location: [#9299](https://github.com/owncloud/web/pull/9299) + + Public links are now being resolved to their actual location if the user has proper access to the + resource (either via space or share). + + https://github.com/owncloud/web/issues/9296 + https://github.com/owncloud/web/pull/9299 + +* Enhancement - Add search location filter: [#9304](https://github.com/owncloud/web/pull/9304) + + We've added the option to set the search location in the searchbar. + + https://github.com/owncloud/web/issues/9258 + https://github.com/owncloud/web/issues/9428 + https://github.com/owncloud/web/issues/9439 + https://github.com/owncloud/web/pull/9304 + https://github.com/owncloud/web/pull/9432 + https://github.com/owncloud/web/pull/9456 + +* Enhancement - Ambiguation for URL view mode params: [#9344](https://github.com/owncloud/web/pull/9344) + + Show tiles view in projects spaces per default, changed route+storage params to be different + from one another. + + https://github.com/owncloud/web/issues/9337 + https://github.com/owncloud/web/pull/9344 + +* Enhancement - Batch actions redesign: [#9346](https://github.com/owncloud/web/pull/9346) + + We've improved the overall look and feel of the top bar batch actions. This includes the new + mechanism that the batch actions show up as well when only one item is selected, but also + includes design changes. + + https://github.com/owncloud/web/issues/9340 + https://github.com/owncloud/web/issues/9352 + https://github.com/owncloud/web/pull/9346 + +* Enhancement - Tag comma separation on client side: [#9348](https://github.com/owncloud/web/pull/9348) + + Tags containing commas are now split up into separate tags on client side to match server side + processing behaviour. + + https://github.com/owncloud/web/issues/9224 + https://github.com/owncloud/web/pull/9348 + +* Enhancement - User notification for blocked pop-ups and redirects: [#9377](https://github.com/owncloud/web/issues/9377) + + We have added some functionality that reminds the user to check their browser settings so that + redirects and e.g. opening a resource in a new tab can work properly. + + https://github.com/owncloud/web/issues/9377 + https://github.com/owncloud/web/pull/9383 + https://github.com/owncloud/web/pull/9419 + +* Enhancement - Allow local storage for auth token: [#9386](https://github.com/owncloud/web/pull/9386) + + We've introduced a new env var WEB_OPTION_TOKEN_STORAGE_LOCAL, when set to true(default), + the auth token will be stored in the browser's local storage instead the session storage, this + will effect in a persisted login state across multiple browser tabs. + + https://github.com/owncloud/web/issues/9325 + https://github.com/owncloud/web/pull/9386 + +* Enhancement - Button styling: [#9394](https://github.com/owncloud/web/pull/9394) + + Button styling (colors and hover colors) has been improved in both light and dark mode to be more + consistent among various button styles (colors for filled and outline buttons) + + https://github.com/owncloud/web/issues/9353 + https://github.com/owncloud/web/issues/9354 + https://github.com/owncloud/web/pull/9394 + https://github.com/owncloud/web/pull/9459 + +* Enhancement - Show local loading spinner in sharing button: [#9423](https://github.com/owncloud/web/pull/9423) + + We've added a loading spinner to the share button in the sharing panel to indicate that its still + processing if action takes longer. + + https://github.com/owncloud/web/issues/9425 + https://github.com/owncloud/web/pull/9423 + +* Enhancement - Add error log to upload dialog: [#9436](https://github.com/owncloud/web/pull/9436) + + We've added a error log to the upload dialog, so if an upload fails the user can copy the log and + might hand it over to their admin. + + https://github.com/owncloud/web/issues/9430 + https://github.com/owncloud/web/pull/9436 + https://github.com/owncloud/web/pull/9426 + +* Enhancement - File versions tooltip with absolute date: [#9441](https://github.com/owncloud/web/pull/9441) + + We've added a tooltip with the absolute date for file versions in file details + + https://github.com/owncloud/web/pull/9441 + Changelog for ownCloud Web [7.0.2] (2023-06-14) ======================================= The following sections list the changes in ownCloud web 7.0.2 relevant to diff --git a/changelog/enhancement-error-notifications-include-x-request-id b/changelog/enhancement-error-notifications-include-x-request-id new file mode 100644 index 00000000000..bc2ea93c9a3 --- /dev/null +++ b/changelog/enhancement-error-notifications-include-x-request-id @@ -0,0 +1,11 @@ +Enhancement: Error notifications include x-request-id + +We've added a details box for the notifications, that pop up if an operation (e.g upload, download, add a share) fails. +This box contains the x-request-id and may help to debug the error on the server side. + +https://github.com/owncloud/web/pull/9482 +https://github.com/owncloud/web/pull/9474 +https://github.com/owncloud/web/pull/9466 +https://github.com/owncloud/web/pull/9448 +https://github.com/owncloud/web/pull/9426 +https://github.com/owncloud/web/issues/9449 diff --git a/changelog/unreleased/bugfix-authenticated-public-links-breaking-uploads b/changelog/unreleased/bugfix-authenticated-public-links-breaking-uploads new file mode 100644 index 00000000000..1c5625f6cf4 --- /dev/null +++ b/changelog/unreleased/bugfix-authenticated-public-links-breaking-uploads @@ -0,0 +1,6 @@ +Bugfix: Authenticated public links breaking uploads + +Opening public links in an authenticated context no longer breaks uploading resources. + +https://github.com/owncloud/web/pull/9299 +https://github.com/owncloud/web/issues/9298 diff --git a/changelog/unreleased/bugfix-label-alignment b/changelog/unreleased/bugfix-label-alignment new file mode 100644 index 00000000000..6965ae1beca --- /dev/null +++ b/changelog/unreleased/bugfix-label-alignment @@ -0,0 +1,6 @@ +Bugfix: Shared with action menu label alignment + +Adjusted offset of alignment of label for shared with action menu option in Sidebar. + +https://github.com/owncloud/web/pull/9324 +https://github.com/owncloud/web/issues/9323 diff --git a/changelog/unreleased/bugfix-limit-relative-user-quota-two-decimals b/changelog/unreleased/bugfix-limit-relative-user-quota-two-decimals new file mode 100644 index 00000000000..60ddbf44fbd --- /dev/null +++ b/changelog/unreleased/bugfix-limit-relative-user-quota-two-decimals @@ -0,0 +1,7 @@ +Bugfix: Relative user quota display limited to two decimals + +If the relative user quota is being reported too precisely from the backend, there was a chance of small display issue. +This has been resolved by limiting the number of decimals that get displayed for the relative quota to two. + +https://github.com/owncloud/web/issues/9259 +https://github.com/owncloud/web/pull/9369 diff --git a/changelog/unreleased/bugfix-media-controls-overflow b/changelog/unreleased/bugfix-media-controls-overflow new file mode 100644 index 00000000000..fa0201a5ac5 --- /dev/null +++ b/changelog/unreleased/bugfix-media-controls-overflow @@ -0,0 +1,6 @@ +Bugfix: Media controls overflow on mobile screens + +Media controls overflowed on smaller 9:16 screens because it is absolutely positioned and centered using transform property, its margin caused the transform operation to not calculate the center properly (the latter also affected desktop but was merely visible). + +https://github.com/owncloud/web/pull/9351 +https://github.com/owncloud/web/issues/9318 diff --git a/changelog/unreleased/bugfix-merging-folders b/changelog/unreleased/bugfix-merging-folders new file mode 100644 index 00000000000..9b1b2e22591 --- /dev/null +++ b/changelog/unreleased/bugfix-merging-folders @@ -0,0 +1,6 @@ +Bugfix: Merging folders + +Merging folders as option to handle name conflicts has been fixed. + +https://github.com/owncloud/web/issues/9461 +https://github.com/owncloud/web/pull/9477 diff --git a/changelog/unreleased/bugfix-remember-location-after-token-invalidation b/changelog/unreleased/bugfix-remember-location-after-token-invalidation new file mode 100644 index 00000000000..f6230b55440 --- /dev/null +++ b/changelog/unreleased/bugfix-remember-location-after-token-invalidation @@ -0,0 +1,6 @@ +Bugfix: Remember location after token invalidation + +Fixed an issue where token invalidation in the IDP would result in losing the current location. So logging in after token invalidation now correctly redirects to the page the user was before. + +https://github.com/owncloud/web/issues/9261 +https://github.com/owncloud/web/pull/9364 diff --git a/changelog/unreleased/bugfix-space-editors-see-empty-trashbin-and-delete-actions-in-space-trashbin b/changelog/unreleased/bugfix-space-editors-see-empty-trashbin-and-delete-actions-in-space-trashbin new file mode 100644 index 00000000000..bc444a331ad --- /dev/null +++ b/changelog/unreleased/bugfix-space-editors-see-empty-trashbin-and-delete-actions-in-space-trashbin @@ -0,0 +1,7 @@ +Bugfix: Space editors see empty trashbin and delete actions in space trashbin + +We've fixed a bug, where space editors were able to see the empty trashbin and delete buttons in the space's trashbin. +This is only allowed for space managers. + +https://github.com/owncloud/web/pull/9389 +https://github.com/owncloud/web/issues/9385 diff --git a/packages/design-system/changelog/unreleased/bugfix-switch-columns-shared-with-me-view b/changelog/unreleased/bugfix-switch-columns-shared-with-me-view similarity index 100% rename from packages/design-system/changelog/unreleased/bugfix-switch-columns-shared-with-me-view rename to changelog/unreleased/bugfix-switch-columns-shared-with-me-view diff --git a/changelog/unreleased/bugfix-uploading-to-folders-with-special-chars b/changelog/unreleased/bugfix-uploading-to-folders-with-special-chars new file mode 100644 index 00000000000..82216eff6f6 --- /dev/null +++ b/changelog/unreleased/bugfix-uploading-to-folders-with-special-chars @@ -0,0 +1,6 @@ +Bugfix: Uploading to folders that contain special characters + +Uploading resources to folders that contain special characters in their names has been fixed. + +https://github.com/owncloud/web/issues/9247 +https://github.com/owncloud/web/pull/9290 diff --git a/changelog/unreleased/enhancement-absolute-date-file-versions b/changelog/unreleased/enhancement-absolute-date-file-versions new file mode 100644 index 00000000000..a46d774c1a8 --- /dev/null +++ b/changelog/unreleased/enhancement-absolute-date-file-versions @@ -0,0 +1,5 @@ +Enhancement: File versions tooltip with absolute date + +We've added a tooltip with the absolute date for file versions in file details + +https://github.com/owncloud/web/pull/9441 diff --git a/changelog/unreleased/enhancement-access-denied-page-change-message b/changelog/unreleased/enhancement-access-denied-page-change-message new file mode 100644 index 00000000000..ed7fda36c67 --- /dev/null +++ b/changelog/unreleased/enhancement-access-denied-page-change-message @@ -0,0 +1,6 @@ +Enhancement: Access denied page update message + +We've updated the access denied page message to be more specific and give the user a link to a help page. + +https://github.com/owncloud/web/pull/9263 +https://github.com/owncloud/web/issues/9051 diff --git a/changelog/unreleased/enhancement-add-error-log-to-upload-dialog b/changelog/unreleased/enhancement-add-error-log-to-upload-dialog new file mode 100644 index 00000000000..23443261193 --- /dev/null +++ b/changelog/unreleased/enhancement-add-error-log-to-upload-dialog @@ -0,0 +1,9 @@ +Enhancement: Add error log to upload dialog + +We've added a error log to the upload dialog, so if an upload fails the user can copy the log and might hand +it over to their admin. + +https://github.com/owncloud/web/pull/9436 +https://github.com/owncloud/web/pull/9426 +https://github.com/owncloud/web/issues/9430 + diff --git a/changelog/unreleased/enhancement-add-location-filter-search b/changelog/unreleased/enhancement-add-location-filter-search new file mode 100644 index 00000000000..b3c2b2a1beb --- /dev/null +++ b/changelog/unreleased/enhancement-add-location-filter-search @@ -0,0 +1,10 @@ +Enhancement: Add search location filter + +We've added the option to set the search location in the searchbar. + +https://github.com/owncloud/web/pull/9304 +https://github.com/owncloud/web/pull/9432 +https://github.com/owncloud/web/pull/9456 +https://github.com/owncloud/web/issues/9258 +https://github.com/owncloud/web/issues/9428 +https://github.com/owncloud/web/issues/9439 diff --git a/changelog/unreleased/enhancement-allow-local-storage-for-auth-token b/changelog/unreleased/enhancement-allow-local-storage-for-auth-token new file mode 100644 index 00000000000..c7812e0fdc0 --- /dev/null +++ b/changelog/unreleased/enhancement-allow-local-storage-for-auth-token @@ -0,0 +1,8 @@ +Enhancement: Allow local storage for auth token + +We've introduced a new env var WEB_OPTION_TOKEN_STORAGE_LOCAL, when set to true(default), the auth token will be stored in the +browser's local storage instead the session storage, this will effect in a persisted login state across multiple +browser tabs. + +https://github.com/owncloud/web/pull/9386 +https://github.com/owncloud/web/issues/9325 diff --git a/changelog/unreleased/enhancement-ambiguation-for-url-view-mode-params b/changelog/unreleased/enhancement-ambiguation-for-url-view-mode-params new file mode 100644 index 00000000000..605fde15e58 --- /dev/null +++ b/changelog/unreleased/enhancement-ambiguation-for-url-view-mode-params @@ -0,0 +1,6 @@ +Enhancement: Ambiguation for URL view mode params + +Show tiles view in projects spaces per default, changed route+storage params to be different from one another. + +https://github.com/owncloud/web/pull/9344 +https://github.com/owncloud/web/issues/9337 diff --git a/changelog/unreleased/enhancement-batch-actions-redesign b/changelog/unreleased/enhancement-batch-actions-redesign new file mode 100644 index 00000000000..40f6dcf9a34 --- /dev/null +++ b/changelog/unreleased/enhancement-batch-actions-redesign @@ -0,0 +1,9 @@ +Enhancement: Batch actions redesign + +We've improved the overall look and feel of the top bar batch actions. +This includes the new mechanism that the batch actions show up as well when only one item is selected, +but also includes design changes. + +https://github.com/owncloud/web/pull/9346 +https://github.com/owncloud/web/issues/9340 +https://github.com/owncloud/web/issues/9352 diff --git a/changelog/unreleased/enhancement-button-styling b/changelog/unreleased/enhancement-button-styling new file mode 100644 index 00000000000..ae29986e611 --- /dev/null +++ b/changelog/unreleased/enhancement-button-styling @@ -0,0 +1,8 @@ +Enhancement: Button styling + +Button styling (colors and hover colors) has been improved in both light and dark mode to be more consistent among various button styles (colors for filled and outline buttons) + +https://github.com/owncloud/web/pull/9394 +https://github.com/owncloud/web/pull/9459 +https://github.com/owncloud/web/issues/9353 +https://github.com/owncloud/web/issues/9354 diff --git a/changelog/unreleased/enhancement-cloud-import b/changelog/unreleased/enhancement-cloud-import index 0f0a8de995b..c5c3714723a 100644 --- a/changelog/unreleased/enhancement-cloud-import +++ b/changelog/unreleased/enhancement-cloud-import @@ -3,7 +3,15 @@ Enhancement: Cloud import An action to import files from other external cloud providers has been added. OneDrive is currently the only supported option. https://github.com/owncloud/web/issues/9151 +https://github.com/owncloud/web/issues/9445 +https://github.com/owncloud/web/issues/9469 +https://github.com/owncloud/web/issues/9454 +https://github.com/owncloud/web/issues/9486 https://github.com/owncloud/web/pull/9150 https://github.com/owncloud/web/pull/9282 https://github.com/owncloud/web/pull/9291 - +https://github.com/owncloud/web/pull/9374 +https://github.com/owncloud/web/pull/9460 +https://github.com/owncloud/web/pull/9471 +https://github.com/owncloud/web/pull/9470 +https://github.com/owncloud/web/pull/9487 diff --git a/changelog/unreleased/enhancement-improve-extension-app-topbar b/changelog/unreleased/enhancement-improve-extension-app-topbar new file mode 100644 index 00000000000..fa229e93937 --- /dev/null +++ b/changelog/unreleased/enhancement-improve-extension-app-topbar @@ -0,0 +1,7 @@ +Enhancement: Improve extension app topbar + +We have redesigned the app topbar used in extensions and added more file actions. + +https://github.com/owncloud/web/issues/8422 +https://github.com/owncloud/web/pull/8442 +https://github.com/owncloud/web/pull/8447 diff --git a/changelog/unreleased/enhancement-pop-up-redirect-blocker-notification b/changelog/unreleased/enhancement-pop-up-redirect-blocker-notification new file mode 100644 index 00000000000..0ab698f1d99 --- /dev/null +++ b/changelog/unreleased/enhancement-pop-up-redirect-blocker-notification @@ -0,0 +1,8 @@ +Enhancement: User notification for blocked pop-ups and redirects + +We have added some functionality that reminds the user to check their browser settings +so that redirects and e.g. opening a resource in a new tab can work properly. + +https://github.com/owncloud/web/issues/9377 +https://github.com/owncloud/web/pull/9383 +https://github.com/owncloud/web/pull/9419 diff --git a/changelog/unreleased/enhancement-resolve-public-link-to-actual-location b/changelog/unreleased/enhancement-resolve-public-link-to-actual-location new file mode 100644 index 00000000000..575d28e6a83 --- /dev/null +++ b/changelog/unreleased/enhancement-resolve-public-link-to-actual-location @@ -0,0 +1,6 @@ +Enhancement: Resolve pulic links to their actual location + +Public links are now being resolved to their actual location if the user has proper access to the resource (either via space or share). + +https://github.com/owncloud/web/pull/9299 +https://github.com/owncloud/web/issues/9296 diff --git a/changelog/unreleased/enhancement-scroll-to-created-folder b/changelog/unreleased/enhancement-scroll-to-created-folder new file mode 100644 index 00000000000..7c86f33237c --- /dev/null +++ b/changelog/unreleased/enhancement-scroll-to-created-folder @@ -0,0 +1,7 @@ +Enhancement: Scroll to newly created folder + +After creating a new folder that gets sorted into the currently displayed resources but outside of the current viewport, we now scroll to the new folder. + +https://github.com/owncloud/web/issues/7600 +https://github.com/owncloud/web/issues/7601 +https://github.com/owncloud/web/pulls/8145 diff --git a/changelog/unreleased/enhancement-search-fulltext-filter b/changelog/unreleased/enhancement-search-fulltext-filter index e5ebf22807e..fa3dcd6c178 100644 --- a/changelog/unreleased/enhancement-search-fulltext-filter +++ b/changelog/unreleased/enhancement-search-fulltext-filter @@ -5,4 +5,5 @@ The search result page now has a full-text filter which can be used to filter th https://github.com/owncloud/web/pull/9059 https://github.com/owncloud/web/pull/9087 https://github.com/owncloud/web/pull/9096 +https://github.com/owncloud/web/pull/9332 https://github.com/owncloud/web/issues/9058 diff --git a/changelog/unreleased/enhancement-show-spinner-sharing-button b/changelog/unreleased/enhancement-show-spinner-sharing-button new file mode 100644 index 00000000000..55835a8f9d3 --- /dev/null +++ b/changelog/unreleased/enhancement-show-spinner-sharing-button @@ -0,0 +1,7 @@ +Enhancement: Show local loading spinner in sharing button + +We've added a loading spinner to the share button in the sharing panel +to indicate that its still processing if action takes longer. + +https://github.com/owncloud/web/pull/9423 +https://github.com/owncloud/web/issues/9425 diff --git a/changelog/unreleased/enhancement-single-file-link-open-with-default-app b/changelog/unreleased/enhancement-single-file-link-open-with-default-app index f0b52a9929a..5ac866eb2ba 100644 --- a/changelog/unreleased/enhancement-single-file-link-open-with-default-app +++ b/changelog/unreleased/enhancement-single-file-link-open-with-default-app @@ -4,4 +4,5 @@ We've added a configurable functionality, that a single shared file via link wil for example text-editor. https://github.com/owncloud/web/pull/9046 +https://github.com/owncloud/web/pull/9307 https://github.com/owncloud/web/issues/9045 diff --git a/changelog/unreleased/enhancement-tag-comma-separation b/changelog/unreleased/enhancement-tag-comma-separation new file mode 100644 index 00000000000..83bb4712432 --- /dev/null +++ b/changelog/unreleased/enhancement-tag-comma-separation @@ -0,0 +1,6 @@ +Enhancement: Tag comma separation on client side + +Tags containing commas are now split up into separate tags on client side to match server side processing behaviour. + +https://github.com/owncloud/web/pull/9348 +https://github.com/owncloud/web/issues/9224 diff --git a/dev/docker/ocis.web.config.json b/dev/docker/ocis.web.config.json index c0c98de06e9..401410c67ff 100644 --- a/dev/docker/ocis.web.config.json +++ b/dev/docker/ocis.web.config.json @@ -28,7 +28,15 @@ ], "contextHelpersReadMore": true }, - "apps": ["files", "text-editor", "pdf-viewer", "search", "external", "admin-settings", "webfinger"], + "apps": [ + "files", + "text-editor", + "pdf-viewer", + "search", + "external", + "admin-settings", + "webfinger" + ], "external_apps": [ { "id": "preview", @@ -44,6 +52,13 @@ "url": "https://embed.diagrams.net", "theme": "minimal" } + }, + { + "id": "importer", + "path": "web-app-importer", + "config": { + "companionUrl": "https://host.docker.internal:9200/companion" + } } ] } diff --git a/docker-compose.yml b/docker-compose.yml index 756e74a5f99..bc6edcd4903 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,6 +61,7 @@ services: # TIKA SEARCH_EXTRACTOR_TYPE: "tika" SEARCH_EXTRACTOR_TIKA_TIKA_URL: "http://host.docker.internal:9998" + SEARCH_EXTRACTOR_CS3SOURCE_INSECURE: "true" volumes: - ./dev/docker/ocis.entrypoint.sh:/usr/bin/entrypoint - ./dev/docker/ocis.idp.config.yaml:/etc/ocis/idp.yaml @@ -76,6 +77,7 @@ services: traefik.http.routers.ocis.entrypoints: websecure # workaround: https://github.com/owncloud/ocis/issues/5108 traefik.http.routers.ocis.middlewares: cors + restart: unless-stopped depends_on: - traefik - tika-service @@ -181,15 +183,18 @@ services: - host.docker.internal:${DOCKER_HOST:-host-gateway} companion: - image: transloadit/companion:4.5.0 + image: owncloud/uppy-companion:3.12.13-owncloud extra_hosts: - host.docker.internal:${DOCKER_HOST:-host-gateway} environment: NODE_TLS_REJECT_UNAUTHORIZED: 0 + COMPANION_ALLOW_LOCAL_URLS: "true" COMPANION_DATADIR: /tmp/companion/ COMPANION_DOMAIN: host.docker.internal:9200 COMPANION_PROTOCOL: https COMPANION_PATH: /companion + COMPANION_ONEDRIVE_KEY: "${COMPANION_ONEDRIVE_KEY}" + COMPANION_ONEDRIVE_SECRET: "${COMPANION_ONEDRIVE_SECRET}" volumes: - uppy_companion_datadir:/tmp/companion/ labels: @@ -249,7 +254,7 @@ services: container_name: web_tika ports: - 9998:9998 - restart: always + restart: unless-stopped volumes: uploads: diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index 2ea0ee501a0..00000000000 --- a/docs/development.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: "Development" -date: 2022-01-28T00:00:00+00:00 -weight: 50 -geekdocRepo: https://github.com/owncloud/web -geekdocEditPath: edit/master/docs -geekdocFilePath: development.md ---- - -{{< toc >}} - -This is a collection of tips and conventions to follow when working on [the ownCloud web frontend](https://github.com/owncloud/web). -It is a living document, please open a PR if you find something missing. - -## Contributing to ownCloud web - -Everyone is invited to contribute. Simply fork [the codebase](), check [the issues](https://github.com/owncloud/web/issues?q=is%3Aopen+is%3Aissue+label%3ATopic%3Agood-first-issue) for a suitable one and open a pull request! - -### Linting and tests - -To make sure your pull request can be efficiently reviewed and won't need a lot of changes down the road, please run the linter and the unit tests via `pnpm lint --fix` and `pnpm test:unit` locally. [Our CI](https://drone.owncloud.com/owncloud/web) will run on pull requests and report back any problems after that. For a further introduction on how we handle testing, please head to the [testing docs]({{< ref "testing/_index.md" >}}). - -### Changelog items - -In our project, we follow [SemVer](https://semver.org/) and keep a changelog for every change that influences the user experience (where "users" can be admins, end-users and extension developers). -Some changes, like refactoring, updating dependencies or adding tests don't require a changelog item. - -Please add a changelog item to the `changelog/unreleased/` folder, referencing the issue and pull request numbers, following the [changelog item template](https://github.com/owncloud/web/blob/master/changelog/TEMPLATE). - -## Code Conventions - -### Early returns - -We're trying to stick with early returns in our code to make it more performant and simpler to reason about it. - -### Translations - -Use `v-translate` (or `v-text` in combination with computed properties) inside HTML tags (instead of a `` or similar) in order to make reasoning about the DOM tree easier. - -### TypeScript - -We're currently migrating our projects from JavaScript to TypeScript, providing for type safety. cleaner interfaces and making sure our IDEs can support us in reasoning about our (ever growing, complex) codebase. - -### Dependencies - -To keep the bundle size small and reduce the risk of introducing security problems for our users, we try to limit the amount of dependencies in our projects and keep them as up-to-date as possible. diff --git a/docs/development/_index.md b/docs/development/_index.md new file mode 100644 index 00000000000..17455cd3e6e --- /dev/null +++ b/docs/development/_index.md @@ -0,0 +1,16 @@ +--- +title: 'Development' +date: 2022-01-28T00:00:00+00:00 +weight: 50 +geekdocRepo: https://github.com/owncloud/web +geekdocEditPath: edit/master/docs/development +geekdocFilePath: _index.md +geekdocCollapseSection: true +--- + +{{< toc >}} + +This section is a guide about the development of ownCloud Web **core**, **apps** and **extensions**. +This includes **tooling**, **conventions** and the **repo structure**. + +It is of interest for you if you want to contribute to ownCloud Web or develop your own apps and extensions. diff --git a/docs/development/conventions.md b/docs/development/conventions.md new file mode 100644 index 00000000000..f5e0212dd9f --- /dev/null +++ b/docs/development/conventions.md @@ -0,0 +1,63 @@ +--- +title: "Conventions" +date: 2022-01-28T00:00:00+00:00 +weight: 20 +geekdocRepo: https://github.com/owncloud/web +geekdocEditPath: edit/master/docs/development +geekdocFilePath: conventions.md +--- + +{{< toc >}} + +This is a collection of tips and conventions to follow when working on the [ownCloud web frontend](https://github.com/owncloud/web). +Since it is a living document, please open a PR if you find something missing. + +## Contributing to ownCloud Web + +Everyone is invited to contribute. Simply fork the [codebase](https://github.com/owncloud/web/), +check the [issues](https://github.com/owncloud/web/issues?q=is%3Aopen+is%3Aissue+label%3ATopic%3Agood-first-issue) +for a suitable one and open a pull request! + +### Linting and Tests + +To make sure your pull request can be efficiently reviewed and won't need a lot of changes down the road, please run the linter and +the unit tests via `pnpm lint --fix` and `pnpm test:unit` locally. Our [CI](https://drone.owncloud.com/owncloud/web) will run on +pull requests and report back any problems after that. For a further introduction on how we handle testing, please head to +the [testing docs]({{< ref "../testing/_index.md" >}}). + +### Changelog Items + +In our project, we follow [SemVer](https://semver.org/) and keep a changelog for every change that influences the user experience (where +"users" can be admins, end-users and developers). +Some changes, like refactoring, updating dependencies, writing documentation or adding tests don't require a changelog item. + +Please add a changelog item to the `changelog/unreleased/` folder, referencing the issue and pull request numbers, following +the [changelog item template](https://github.com/owncloud/web/blob/master/changelog/TEMPLATE). + +## Code Conventions + +### Early Returns + +We're trying to stick with early returns in our code to make it more performant and simpler to reason about it. + +### Translations + +Use the `v-text` directive in combination with `$gettext` (or a variation of it) inside HTML tags (instead of +a `` or similar) in order to make reasoning about the DOM tree easier. + +### TypeScript + +We're using TypeScript, which allows us to catch bugs at transpile time. Clean types make sure our IDEs can support us +in reasoning about our (ever growing, complex) codebase. + +### Vue 3 and Composition API + +We've migrated from Vue 2 to Vue 3 late in 2022 and since then have been investing continuous efforts to move away from the Vue options API +in favor of the Vue composition API. The `web-pkg` helper package provides quite some composables which will help you in +app & extension development, so we encourage you to make use of the Vue composition API as well, even outside of the +ownCloud Web repository. + +### Dependencies + +To keep the bundle size small and reduce the risk of introducing security problems for our users, we try to limit +the amount of dependencies in our code base and keep them as up-to-date as possible. diff --git a/docs/development/repo-structure.md b/docs/development/repo-structure.md new file mode 100644 index 00000000000..720dfba07bf --- /dev/null +++ b/docs/development/repo-structure.md @@ -0,0 +1,133 @@ +--- +title: 'Repo structure and (published) packages' +date: 2022-01-28T00:00:00+00:00 +weight: 30 +geekdocRepo: https://github.com/owncloud/web +geekdocEditPath: edit/master/docs/development +geekdocFilePath: repo-structure.md +--- + +{{< toc >}} + +## Repository Structure + +From a developer's perspective, the most important parts of the [ownCloud Web repo](https://github.com/owncloud/web) are the following files and folders: + +### dev Folder and docker-compose.yml File + +The `/dev` folder contains all the configuration files that are needed in the `docker-compose.yml` file. This docker compose stack +contains all the backend and testing related infrastructure that is needed for an out-of-the-box usable localhost development setup, +as described in the [tooling section]({{< ref "tooling.md" >}}). + +### docs Folder + +Within the `/docs` folder you will find all the documentation source documents that get published to the [dev docs](https://owncloud.dev/clients/web/). + +### packages Folder + +We're using the [ownCloud Web repo](https://github.com/owncloud/web) as a mono repo. It contains a variety of packages. Some of them get +published to [npmjs.com](https://npmjs.com), others define the core packages, apps and extensions that are the foundation of +the `ownCloud Web` release artifact. + +Having these packages side by side within the `/packages` folder of the repo is possible because of a `pnpm` feature called `Workspaces`. You can learn more about that by visiting the [pnpm docs](https://pnpm.io/workspaces). + +### tests Folder + +For historic reasons, there are two end-to-end test collections in the ownCloud Web project. The older one lives in `/tests/acceptance`, +using [Nightwatch.js](https://nightwatchjs.org). The newer one lives in `/tests/e2e`, using [Playwright](https://playwright.dev). +We're slowly moving away from the nightwatch.js-based e2e tests. As our coverage with playwright based e2e tests grows continuously, it might +happen that you will not find the `/tests/acceptance` folder anymore. If that's the case at the time of reading this, those are happy times. +You're more than welcome to make a pull request and adjust this section of the docs accordingly. :-) + +The rest of the folder contains helpers and configuration files for our automated testing infrastructure. + +You can read more about testing in our [testing section]({{< ref "../testing/_index.md" >}}) + +### package.json File + +This is probably no surprise: the root level `package.json` file defines the project information, build scripts, dependencies and some more details. +Each package in `/packages` can and most likely will contain another `package.json` which does the same for the respective package. + +### vite.config.ts + +We're working with [Vite](https://vitejs.dev) as a local development server and build tool. `vite.config.ts` is the main configuration file for that. +You can read more about the usage in our [tooling section]({{< ref "tooling.md" >}}). + +## (Published) Packages + +Each package in the `/packages` folder can - not exclusively, but most commonly - consist of + +- source code (`/src`), +- unit tests (`/tests`), +- translations (`/l10n`) and +- a `package.json` file for package specific details and dependencies. + +### Code Style and Build Config + +Some of our packages in `/packages` are pure helper packages which ensure a common code style and build configuration for all our +internal (mono repo) and external packages. We encourage you to make use of the very same packages. This helps the community +understand code more easily, even when coming from different developers or vendors in the ownCloud Web ecosystem. + +Namely those packages are + +- `/packages/babel-preset` +- `/packages/eslint-config` +- `/packages/extension-sdk` +- `/packages/prettier-config` +- `/packages/tsconfig/` + +### ownCloud Design System + +The ownCloud Design System (`/packages/design-system`) is a collection of components, design tokens and styles which ensure a +unique and consistent look and feel and branding throughout the ownCloud Web ecosystem. We hope that you use it, too, so that your +very own apps and extensions will blend in with all the others. Documentation and code examples can be found in +the [design system documentation](https://owncloud.design). + +The ownCloud Design System is a standalone project, but to make development easier we have the code in our mono repo. +We're planning to publish it on npmjs.com as [@ownclouders/design-system](https://www.npmjs.com/package/@ownclouders/design-system) +as soon as possible. Since it's bundled with ownCloud Web, you should not bundle it with your app or extension. + +### web-client + +The client package (`/packages/web-client`) serves as an abstraction layer for the various ownCloud APIs, like +[LibreGraph](https://owncloud.dev/apis/http/graph/), [WebDAV](https://doc.owncloud.com/server/next/developer_manual/webdav_api/) and +[OCS](https://doc.owncloud.com/server/next/developer_manual/core/apis/ocs-capabilities.html). The package provides TypeScript +interfaces for various entities (like files, folders, shares and spaces) and makes sure that raw API responses are properly +transformed so that you can deal with more useful objects. The web-client package gets published +on npmjs.com as [@ownclouders/web-client](https://www.npmjs.com/package/@ownclouders/web-client). + +Dedicated documentation for the `web-client` package is not available, yet, since our extension system is still work in progress. + +### web-pkg + +The web-pkg package (`/packages/web-pkg`) is a collection of opinionated components, composables, types and other helpers that aim +at making your app and extension developer experience as easy and seamless as possible. The web-pkg package gets published on +npmjs.com as [@ownclouders/web-pkg](https://www.npmjs.com/package/@ownclouders/web-pkg). + +Dedicated documentation for the `web-pkg` package is not available, yet, since our extension system is still work in progress. + +### web-runtime + +At the very heart of ownCloud Web, the `web-runtime` is responsible for dependency injection, app bootstrapping, configuration, +authentication, data preloading and much more. +It is very likely that you will never get in touch with it as most of the developer-facing features are exposed via `web-pkg`. If you +have more questions about this package, please join our [public chat](https://talk.owncloud.com/channel/web) and simply ask +or write an issue in our [issue tracker](https://github.com/owncloud/web/issues). + +### Standalone Core Apps + +Both `web-app-admin-settings` and `web-app-files` are standalone apps which are bundled with the default ownCloud Web release artifact. + +### Viewer and Editor Apps + +Apps which fall into the categories `viewer` or `editor` can be opened from the context of a file or folder. This mostly happens from +within the `files` app. We currently bundle the following apps with the default ownCloud Web release artifact: + +- `web-app-draw-io` an editor for `.drawio` files +- `web-app-external` an iframe integration of all the apps coming from the [app provider](https://owncloud.dev/services/app-provider/) + (e.g. OnlyOffice, Collabora Online and others) +- `web-app-pdf-viewer` a viewer for `.pdf` files, which relies on native PDF rendering support from the browser +- `web-app-preview` a viewer for various media files (audio / video / image formats) +- `web-app-text-editor` a simple editor for `.txt`, `.md` and other plain text files + +If you're interested in writing your own viewer or editor app for certain file types, please get in touch with us for more info. diff --git a/docs/development/tooling.md b/docs/development/tooling.md new file mode 100644 index 00000000000..810dd4241bf --- /dev/null +++ b/docs/development/tooling.md @@ -0,0 +1,45 @@ +--- +title: 'Tooling' +date: 2022-01-28T00:00:00+00:00 +weight: 40 +geekdocRepo: https://github.com/owncloud/web +geekdocEditPath: edit/master/docs/development +geekdocFilePath: tooling.md +--- + +{{< toc >}} + +## Packaging + +Web is using [pnpm](https://pnpm.io/) as package manager and [vite](https://vitejs.dev/) as build tool. The latter is built on top of [rollup](https://rollupjs.org/) and brings some additional features such as instant hot-reloading. + +## Development Setup + +### Prerequisites + +- docker +- docker-compose (if not already included in your docker installation) +- pnpm +- node + +If you’re not using Docker Desktop, you might have to modify your `/etc/hosts` and add `127.0.0.1 host.docker.internal` to make `host.docker.internal` links work. + +### Installing Dependencies + +After cloning the source code, install the dependencies via `pnpm install`. + +### Starting the Server + +Web supports oCIS and oC10 as servers. You can start both of them by running `docker-compose up ocis oc10`. + +Note that `ocis` needs a short while to start because it is waiting for `tika` to be initialized. This is the case as soon as the `tika-service` container has stopped running. + +### Building and Accessing Web + +After the docker containers are running (and `tika` is being initialized), run `pnpm build:w` to build Web. This also includes hot-reloading after changes you make, although it will take a while to rebuild the project. See down below for some details on how to enable instant hot-reloading. + +Now you can access Web via https://host.docker.internal:9200 (oCIS) and http://host.docker.internal:8080 (OC10). + +### Using Instant Hot-Reload via Vite + +To work with instant hot-reloading, you can also build Web by running `pnpm vite`. The ports to access Web are slightly different then: https://host.docker.internal:9201 (oCIS) and http://host.docker.internal:8081 (OC10). Also note that the initial page load may take a bit longer than usual. This is normal and to be expected. diff --git a/docs/getting-started.md b/docs/getting-started.md index 6c7051f4955..0632cdcdf16 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -27,11 +27,13 @@ This setup currently doesn't work on Windows out of the box. After cloning the [source code](https://github.com/owncloud/web), install the dependencies via `pnpm install` and bundle the frontend code by running `pnpm build:w`. -Then, you can start the backends by running `docker-compose up oc10 ocis` and access them via [https://host.docker.internal:9200](https://host.docker.internal:9200) (oCIS) and [http://host.docker.internal:8080](http://host.docker.internal:8080) (OC10). If you're not using Docker Desktop, you might have to modify your `/etc/hosts` and add `172.17.0.1 host.docker.internal` to make the `host.docker.internal` links work. +Then, you can start the backends by running `docker-compose up oc10 ocis` and access them via [https://host.docker.internal:9200](https://host.docker.internal:9200) (oCIS) and [http://host.docker.internal:8080](http://host.docker.internal:8080) (OC10). If you're not using Docker Desktop, you might have to modify your `/etc/hosts` and add `127.0.0.1 host.docker.internal` to make the `host.docker.internal` links work. The bundled frontend code automatically gets mounted into the Docker containers, recompiles on changes and you can log in using the demo user (admin/admin) and take a look around! -### Source code +For more details on how to set up Web for development, please see [tooling]({{< ref "tooling.md#development-setup" >}}). + +### Source Code The source code is hosted at [https://github.com/owncloud/web](https://github.com/owncloud/web). Please refer to the [build documentation for Web]({{< ref "./building.md" >}}). @@ -53,26 +55,34 @@ Depending on the backend you are using, there are sample config files provided i by this is the trash bin, as that doesn't allow showing previews at all. - `options.previewFileMimeTypes` Specifies which mimeTypes will be previewed in the ui. For example to only preview jpg and text files set this option to `["image/jpeg", "text/plain"]`. - `options.accountEditLink` This accepts an object with the following optional fields to have a link on the account page: - - `options.accountEditLink.href` Set a different target URL for the edit link. Make sure to prepend it with `http(s)://`. + - `options.accountEditLink.href` Set a different target URL for the edit link. Make sure to prepend it with `http(s)://`. - `options.disableFeedbackLink` Set this option to `true` to disable the feedback link in the topbar. Keeping it enabled (value `false` or absence of the option) allows ownCloud to get feedback from your user base through a dedicated survey website. - `options.feedbackLink` This accepts an object with the following optional fields to customize the feedback link in the topbar: - - `options.feedbackLink.href` Set a different target URL for the feedback link. Make sure to prepend it with `http(s)://`. Defaults to `https://owncloud.com/web-design-feedback`. - - `options.feedbackLink.ariaLabel` Since the link only has an icon, you can set an e.g. screen reader accessible label. Defaults to `ownCloud feedback survey`. - - `options.feedbackLink.description` Provide any description you want to see as tooltip and as accessible description. Defaults to `Provide your feedback: We'd like to improve the web design and would be happy to hear your feedback. Thank you! Your ownCloud team.` + - `options.feedbackLink.href` Set a different target URL for the feedback link. Make sure to prepend it with `http(s)://`. Defaults to `https://owncloud.com/web-design-feedback`. + - `options.feedbackLink.ariaLabel` Since the link only has an icon, you can set an e.g. screen reader accessible label. Defaults to `ownCloud feedback survey`. + - `options.feedbackLink.description` Provide any description you want to see as tooltip and as accessible description. Defaults to `Provide your feedback: We'd like to improve the web design and would be happy to hear your feedback. Thank you! Your ownCloud team.` - `options.sharingRecipientsPerPage` Sets the amount of users shown as recipients in the dropdown when sharing resources. Default amount is 200. - `options.sidebar.shares.showAllOnLoad` Sets the list of (link) shares list in the sidebar to be initially expanded (default is a collapsed state, only showing the first three shares). -- `options.runningOnEos` Set this option to `true` if running on an [EOS storage backend](https://eos-web.web.cern.ch/eos-web/) to enable its specific features. Defaults to `false`. +- `options.runningOnEos` Set this option to `true` if running on an [EOS storage backend](https://eos-web.web.cern.ch/eos-web/) to enable its specific features. Defaults to `false`, and we recommend reaching out to [the ownCloud web team](https://talk.owncloud.com/channel/web) if you're not CERN and thinking about enabling this feature flag. - `options.cernFeatures` Enabling this will activate CERN-specific features. Defaults to `false`. - `options.hoverableQuickActions` Set this option to `true` to hide the quick actions (buttons appearing on file rows), and only show them when the user hovers the row with his mouse. Defaults to `false`. - `option.routing` This accepts an object with the following fields to customize the routing behaviour: - - `options.routing.idBased` Enable or disable fileIds being added to the URL. Defaults to `true` because otherwise e.g. spaces with name clashes can't be resolved correctly. Only disable this if you can guarantee server side that spaces of the same namespace can't have name clashes. + - `options.routing.idBased` Enable or disable fileIds being added to the URL. Defaults to `true` because otherwise e.g. spaces with name clashes can't be resolved correctly. Only disable this if you can guarantee server side that spaces of the same namespace can't have name clashes. - `options.upload.xhr.timeout` Specifies the timeout for XHR uploads in milliseconds. - `options.editor.autosaveEnabled` Specifies if the autosave for the editor apps is enabled. - `options.editor.autosaveInterval` Specifies the time interval for the autosave of editor apps in seconds. - `options.contextHelpersReadMore` Specifies whether the "Read more" link should be displayed or not. - `options.openLinksWithDefaultApp` Specifies whether single file link shares should be opened with default app or not. +- `options.tokenStorageLocal` Specifies whether the access token will be stored in the local storage when set to `true` or in the session storage when set to `false`. If stored in the local storage, login state will be persisted across multiple browser tabs, means no additional logins are required. Defaults to `true`. + +#### Scripts and Styles + +Web supports adding additional CSS and JavaScript files to further customize the user experience and adapt it to your specific needs. Please consider opening a feature request if you feel like your customization could be a benefit to the upstream project, and keep an eye open for future major releases of `web` since this API may change. + +- `styles` expects an array of objects that specify a `href` attribute, pointing to the path/URL of your stylesheet, like `[{ "href": "css/custom.css" }]`. +- `scripts` expects an array of objects that specify a `src` attribute, pointing to the path/URL of your script, and an optional `async` attribute (defaults to false), like `[{ "src": "js/custom.js", "async": true }]`. ### Sentry diff --git a/docs/testing/testing.md b/docs/testing/testing.md index 85c9d4f5b41..01873c8df7e 100644 --- a/docs/testing/testing.md +++ b/docs/testing/testing.md @@ -1,5 +1,5 @@ --- -title: "Running tests" +title: 'Running tests' date: 2021-07-27T00:00:00+00:00 weight: 60 geekdocRepo: https://github.com/owncloud/web @@ -8,11 +8,12 @@ geekdocFilePath: testing.md --- {{< toc >}} + ## Introduction In order to allow us to make changes quickly, often and with a high level of confidence, we heavily rely on tests within the `web` repository. -All the steps below require you to have the `web` repo cloned locally and dependencies installed. +All the steps below require you to have the `web` repo cloned locally and dependencies installed. This can be achieved by running ```shell @@ -31,33 +32,28 @@ $ pnpm test:unit You can also specify which tests to run by giving a path param, like so: `pnpm test:unit packages//tests/unit/path/to/test.spec.js`. -#### Unit test file structure +#### Unit Test File Structure + Our unit tests spec files follow a simple structure: + - fixtures and mocks at the top - helper functions at the bottom - tests in between -We usually organize tests with nested `describe` blocks. If you would like to get feedback from the core team about -the structure, scope and goals of your unit tests before actually writing some, we invite you to make a pull request -with only `describe` blocks and nested `it.todo("put your test description here")` lines. +We usually organize tests with nested `describe` blocks. If you would like to get feedback from the core team about +the structure, scope and goals of your unit tests before actually writing some, we invite you to make a pull request +with only `describe` blocks and nested `it.todo("put your test description here")` lines. ### E2E Tests (Playwright) -Our end-to-end test suite is built upon the [Playwright Framework](https://github.com/microsoft/playwright), +Our end-to-end test suite is built upon the [Playwright Framework](https://github.com/microsoft/playwright), which makes it easy to write tests, debug them and have them run cross-browser with minimal overhead. -#### Prerequisites - -To run e2e tests with Docker, please make sure you have the following tools installed: +#### Preparation -- docker -- docker-compose (if not already included in your docker installation) -- pnpm -- node +Please make sure you have installed all dependencies and started the server(s) as described in [tooling]({{< ref "tooling.md#development-setup" >}}). -Please also make sure to point `host.docker.internal` to `127.0.0.1` by adding it to your `/etc/hosts` file. - -#### Prepare & start web +#### Prepare & Start Web Bundle the web frontend with the following command: @@ -67,23 +63,7 @@ $ pnpm build:w Our compose setup automatically mounts it into an oC10 and oCIS backend, respectively. Web also gets recompiled on changes. -#### Start Docker - -Using compose, you can start the required Docker containers by running - -For running the test with oc10 run -```shell -$ docker compose up oc10 -``` - -For running the test with ocis run -```shell -$ docker compose up ocis -``` - -and make sure there are no conflicting ports and everything runs smoothly. You can check if everything has worked by opening [https://host.docker.internal:9200](https://host.docker.internal:9200) (oCIS) and [http://host.docker.internal:8080](http://host.docker.internal:8080) (OC10) and logging in using the demo user (admin/admin). - -#### Run E2E tests +#### Run E2E Tests Depending on the backend you want to run the tests on, you can either run @@ -104,6 +84,7 @@ for an **oCIS** backend (filenames including `.oc10` are excluded). To run a particular test, simply add the feature file and line number to the test command, e.g. `pnpm test:e2e:cucumber tests/e2e/cucumber/shareFileJourney.feature:13` Various options are available via ENV variables, e.g. + - `OCIS=true` to run the E2E tests against an oCIS backend - `RETRY=n` to retry failures `n` times - `SLOW_MO=n` to slow the execution time by `n` milliseconds @@ -111,7 +92,7 @@ Various options are available via ENV variables, e.g. - `HEADLESS=bool` to open the browser while the tests run (defaults to true => headless mode) - `BROWSER=name` to run tests against a specific browser. Defaults to Chrome, available are Chrome, Firefox, Webkit, Chromium -For debugging reasons, you may want to record a video or traces of your test run. +For debugging reasons, you may want to record a video or traces of your test run. Again, you can use the following ENV variables in your command: - `REPORT_DIR=another/path` to set a directory for your recorded files (defaults to "reports") @@ -128,8 +109,8 @@ $ npx playwright show-trace path/to/file.zip ### Acceptance Tests (Nightwatch) {{< hint info >}} -We've decided to switch to playwright for end-to-end tests. As we steadily increase the coverage of our playwright -based e2e tests we keep the existing nightwatch based e2e tests maintained. However, we decided to not add new scenarios +We've decided to switch to playwright for end-to-end tests. As we steadily increase the coverage of our playwright +based e2e tests we keep the existing nightwatch based e2e tests maintained. However, we decided to not add new scenarios to the nightwatch based e2e tests anymore. In other words: only continue reading about our nightwatch based acceptance tests below if you need to debug a failing test. @@ -137,18 +118,11 @@ In other words: only continue reading about our nightwatch based acceptance test At ownCloud, we have decided to adopt Docker as the main environment for developing our application. This also applies for running our acceptance tests. -#### Prerequisites +#### Preparation -To run acceptance tests with Docker, please make sure you have the following tools installed: +Please make sure you have installed all dependencies and started the server(s) as described in [tooling]({{< ref "tooling.md#development-setup" >}}). -- docker -- docker-compose (if not already included in your docker installation) -- pnpm -- node - -Please also make sure to point `http://host.docker.internal/` to `127.0.0.1` by adding it to your hosts. - -#### Prepare & start web +#### Prepare & Start Web Bundle the web frontend, which then gets mounted into the respective backends. It also gets recompiled on changes. @@ -158,14 +132,16 @@ $ pnpm build:w #### Start Docker -Using compose, you can start the required Docker containers by running +The acceptance tests need additional docker containers to be running. For running the test with oc10 run + ```shell $ docker compose up oc10 vnc selenium middleware-oc10 ``` For running the test with ocis run + ```shell $ docker compose up ocis vnc selenium middleware-ocis ``` @@ -192,13 +168,11 @@ If you're using a M1 Mac, you need to use `seleniarm/standalone-chromium:4.4.0-2 for oCIS acceptance tests. - -#### Watch the test run +#### Watch the Test Run To watch the tests while running, open [http://host.docker.internal:6080/](http://host.docker.internal:6080/) in the browser to access your VNC client. - -### Analyze the test report +### Analyze the Test Report The cucumber library is used as the test runner for both e2e and acceptance tests. The report generator script lives inside the `tests/e2e/cucumber/report` folder. If you want to create a report after the tests are done, run the command: @@ -213,7 +187,7 @@ node tests/e2e/cucumber/report --report-input=tests/acceptance/report/report.jso ``` By default, the report gets generated to reports/e2e/cucumber/releaseReport/cucumber_report.html. -The location can be changed by adding the ```--report-location``` flag. +The location can be changed by adding the `--report-location` flag. To see all available options run diff --git a/docs/theming/_index.md b/docs/theming/_index.md index 36bbd51f28a..a4ac513155a 100644 --- a/docs/theming/_index.md +++ b/docs/theming/_index.md @@ -1,5 +1,5 @@ --- -title: "Theming" +title: 'Theming' date: 2021-04-01T00:00:00+00:00 weight: 55 geekdocRepo: https://github.com/owncloud/web @@ -82,7 +82,6 @@ Using the `"autoRedirect"` boolean, you can specify whether the user is shown a To further customize your ownCloud instance, you can provide your own styles. To give you an idea of how a working design system looks like, feel free to head over to the [ownCloud design tokens](https://owncloud.design/#/Design%20Tokens) for inspiration. - **Hint:** All the variables are initialized using the [ownCloud design tokens](https://owncloud.design/#/Design%20Tokens) and then overwritten by your theme variables. Therefore, you don't have to provide all the variables and can use the default ownCloud colors as a fallback. In general, the theme loader looks for a `designTokens` key inside your theme configuration. Inside the `designTokens`, it expects to find a `colorPalette`, `fontSizes` and `spacing` collection. The structure is outlined below: @@ -117,8 +116,7 @@ If you define different key-value pairs inside any of the objects in `"designTok If you'd like to set different breakpoints than the default ones in the ownCloud design system, you can set them using theming variables. -Breakpoint variables get prepended with `--oc-breakpoint-`, so e.g. *"large-default"* creates the custom CSS property `--oc-breakpoint-large-default`. - +Breakpoint variables get prepended with `--oc-breakpoint-`, so e.g. _"large-default"_ creates the custom CSS property `--oc-breakpoint-large-default`. ```json { @@ -139,7 +137,7 @@ Breakpoint variables get prepended with `--oc-breakpoint-`, so e.g. *"large-defa For the color values, you can use any valid CSS color format, e.g. **hex** (#fff), **rgb** (rgb(255,255,255)) or **color names** (white). -Color variables get prepended with `--oc-color-`, so e.g. *"background-default"* creates the custom CSS property `--oc-color-background-default`. +Color variables get prepended with `--oc-color-`, so e.g. _"background-default"_ creates the custom CSS property `--oc-color-background-default`. Again, you can use the [ownCloud design tokens](https://owncloud.design/#/Design%20Tokens) as a reference implementation. @@ -168,12 +166,15 @@ Again, you can use the [ownCloud design tokens](https://owncloud.design/#/Design "swatch-inverse-muted": "", "swatch-passive-default": "", "swatch-passive-hover": "", + "swatch-passive-hover-outline": "", "swatch-passive-muted": "", "swatch-passive-contrast": "", "swatch-primary-default": "", "swatch-primary-hover": "", "swatch-primary-muted": "", + "swatch-primary-muted-hover": "", "swatch-primary-gradient": "", + "swatch-primary-gradient-hover": "", "swatch-primary-contrast": "", "swatch-success-default": "", "swatch-success-hover": "", @@ -194,7 +195,7 @@ Again, you can use the [ownCloud design tokens](https://owncloud.design/#/Design You can change the `default`, `large` and `medium` font sizes according to your needs. If you need more customization options regarding font sizes, please [open an issue on GitHub](https://github.com/owncloud/web/issues/new) with a detailed description. -Font size variables get prepended with `--oc-font-size-`, so e.g. *"default"* creates the custom CSS property `--oc-font-size-default`. +Font size variables get prepended with `--oc-font-size-`, so e.g. _"default"_ creates the custom CSS property `--oc-font-size-default`. ```json { @@ -220,10 +221,10 @@ Please note that you also need to make the font available as a `font-face` via C ### Sizes -Use sizing variables to change various UI elements, such as icon and logo appearance, table row or checkbox sizes, according to your needs. +Use sizing variables to change various UI elements, such as icon and logo appearance, table row or checkbox sizes, according to your needs. If you need more customization options regarding sizes, please [open an issue on GitHub](https://github.com/owncloud/web/issues/new) with a detailed description. -Size variables get prepended with `--oc-size-`, so e.g. *"icon-default"* creates the custom CSS property `--oc-size-icon-default`. +Size variables get prepended with `--oc-size-`, so e.g. _"icon-default"_ creates the custom CSS property `--oc-size-icon-default`. ```json { @@ -243,7 +244,7 @@ Size variables get prepended with `--oc-size-`, so e.g. *"icon-default"* creates Use the six spacing options (`xsmall | small | medium | large | xlarge | xxlarge`) to create a more (or less) condensed version of the user interface. If you need more customization options regarding sizes, please [open an issue on GitHub](https://github.com/owncloud/web/issues/new) with a detailed description. -Spacing variables get prepended with `--oc-space-`, so e.g. *"xlarge"* creates the custom CSS property `--oc-space-xlarge`. +Spacing variables get prepended with `--oc-space-`, so e.g. _"xlarge"_ creates the custom CSS property `--oc-space-xlarge`. ```json { @@ -264,7 +265,7 @@ An empty template for your custom theme is provided below, and you can use the i ```json { - "common": { + "common": { "name": "", "slogan": "", "logo": "" diff --git a/packages/web-container/oidc-callback.html b/oidc-callback.html similarity index 97% rename from packages/web-container/oidc-callback.html rename to oidc-callback.html index ee4ee43141f..6c931d69f29 100644 --- a/packages/web-container/oidc-callback.html +++ b/oidc-callback.html @@ -1,5 +1,6 @@ + + + + + +Component to display error log. +```js + + + +``` + diff --git a/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue b/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue index fa35752dcf3..db6dc88ccf3 100644 --- a/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue +++ b/packages/design-system/src/components/OcFilterChip/OcFilterChip.vue @@ -1,5 +1,8 @@ diff --git a/packages/web-app-files/src/components/FilesList/ListInfo.vue b/packages/web-app-files/src/components/FilesList/ListInfo.vue index 3138edc27e9..49df2730e06 100644 --- a/packages/web-app-files/src/components/FilesList/ListInfo.vue +++ b/packages/web-app-files/src/components/FilesList/ListInfo.vue @@ -71,10 +71,11 @@ export default defineComponent({ } ) const itemSize = formatFileSize(this.size, this.$language.current) + const size = parseFloat(this.size?.toString()) let translated if (this.showSpaces) { translated = - this.size > 0 + size > 0 ? this.$ngettext( '%{ itemsCount } item with %{ itemSize } in total (%{ filesStr}, %{foldersStr}, %{spacesStr})', '%{ itemsCount } items with %{ itemSize } in total (%{ filesStr}, %{foldersStr}, %{spacesStr})', @@ -87,7 +88,7 @@ export default defineComponent({ ) } else { translated = - this.size > 0 + size > 0 ? this.$ngettext( '%{ itemsCount } item with %{ itemSize } in total (%{ filesStr}, %{foldersStr})', '%{ itemsCount } items with %{ itemSize } in total (%{ filesStr}, %{foldersStr})', diff --git a/packages/web-app-files/src/components/FilesList/ResourceTable.vue b/packages/web-app-files/src/components/FilesList/ResourceTable.vue index 3c68ca2e3e4..5b0657f9e17 100644 --- a/packages/web-app-files/src/components/FilesList/ResourceTable.vue +++ b/packages/web-app-files/src/components/FilesList/ResourceTable.vue @@ -17,6 +17,7 @@ :sort-by="sortBy" :sort-dir="sortDir" :lazy="lazyLoading" + :grouping-settings="groupingSettings" padding-x="medium" @highlight="fileClicked" @row-mounted="rowMounted" @@ -67,6 +68,9 @@ :is-resource-clickable="isResourceClickable(item.id)" :folder-link="folderLink(item)" :parent-folder-link="parentFolderLink(item)" + :parent-folder-link-icon-additional-attributes=" + parentFolderLinkIconAdditionalAttributes(item) + " :class="{ 'resource-table-resource-cut': isResourceCut(item) }" @click="emitFileClick(item)" /> @@ -245,10 +249,13 @@ import { } from 'web-app-files/src/router' import get from 'lodash-es/get' +// ODS component import is necessary here for CERN to overwrite OcTable +import OcTable from 'design-system/src/components/OcTable/OcTable.vue' + const TAGS_MINIMUM_SCREEN_WIDTH = 850 export default defineComponent({ - components: { ContextMenuQuickAction }, + components: { ContextMenuQuickAction, OcTable }, props: { /** * Resources to be displayed in the table. @@ -420,6 +427,14 @@ export default defineComponent({ type: Object as PropType, required: false, default: null + }, + /** + * This is only relevant for CERN and can be ignored in any other cases. + */ + groupingSettings: { + type: Object, + required: false, + default: null } }, emits: [ @@ -562,6 +577,15 @@ export default defineComponent({ wrap: 'nowrap', width: 'shrink' }, + { + name: 'status', + prop: 'disabled', + title: this.$gettext('Status'), + type: 'slot', + alignH: 'right', + wrap: 'nowrap', + width: 'shrink' + }, this.hasTags ? { name: 'tags', @@ -753,6 +777,21 @@ export default defineComponent({ resource: file }) }, + parentFolderLinkIconAdditionalAttributes(file) { + // Identify if resource is project space or is part of a project space and the resource is located in its root + if ( + isProjectSpaceResource(file) || + (isProjectSpaceResource(this.getInternalSpace(file.storageId) || ({} as Resource)) && + file.path.split('/').length === 2) + ) { + return { + name: 'layout-grid', + 'fill-type': 'fill' + } + } + + return {} + }, fileDragged(file) { this.addSelectedResource(file) }, diff --git a/packages/web-app-files/src/components/Search/List.vue b/packages/web-app-files/src/components/Search/List.vue index 6e2dcaeaa70..3dedb02a5c2 100644 --- a/packages/web-app-files/src/components/Search/List.vue +++ b/packages/web-app-files/src/components/Search/List.vue @@ -144,6 +144,12 @@ import { eventBus, useCapabilityFilesFullTextSearch } from 'web-pkg' import ItemFilter from 'web-pkg/src/components/ItemFilter.vue' import { isLocationCommonActive } from 'web-app-files/src/router' import ItemFilterToggle from 'web-pkg/src/components/ItemFilterToggle.vue' +import { useKeyboardActions } from 'web-pkg/src/composables/keyboardActions' +import { + useKeyboardTableNavigation, + useKeyboardTableMouseActions, + useKeyboardTableActions +} from 'web-app-files/src/composables/keyboardActions' const visibilityObserver = new VisibilityObserver() @@ -190,6 +196,16 @@ export default defineComponent({ const fullTextSearchEnabled = useCapabilityFilesFullTextSearch() const searchTermQuery = useRouteQuery('term') + const scopeQuery = useRouteQuery('scope') + const doUseScope = useRouteQuery('useScope') + + const resourcesView = useResourcesViewDefaults() + + const keyActions = useKeyboardActions('files-view') + useKeyboardTableNavigation(keyActions, resourcesView.paginatedResources) + useKeyboardTableMouseActions(keyActions) + useKeyboardTableActions(keyActions) + const searchTerm = computed(() => { return queryItemAsString(unref(searchTermQuery)) }) @@ -234,6 +250,9 @@ export default defineComponent({ if (fullTextQuery) { term = `+Content:"${unref(searchTerm)}"` } + if (unref(scopeQuery) && unref(doUseScope) === 'true') { + term += ` scope:${unref(scopeQuery)}` + } const tagsQuery = queryItemAsString(unref(tagParam)) if (tagsQuery) { @@ -275,11 +294,10 @@ export default defineComponent({ watch( () => unref(route).query, (newVal, oldVal) => { - const filters = ['q_fullText', 'q_tags'] + const filters = ['q_fullText', 'q_tags', 'useScope'] const isChange = newVal?.term !== oldVal?.term || filters.some((f) => newVal[f] ?? undefined !== oldVal[f] ?? undefined) - if (isChange && isLocationCommonActive(router, 'files-common-search')) { emit('search', buildSearchTerm(true)) } @@ -289,7 +307,7 @@ export default defineComponent({ return { ...useFileActions({ store }), - ...useResourcesViewDefaults(), + ...resourcesView, loadAvailableTagsTask, fileListHeaderY, fullTextSearchEnabled, diff --git a/packages/web-app-files/src/components/Search/Preview.vue b/packages/web-app-files/src/components/Search/Preview.vue index c1def68a98d..7af8a8b96c2 100644 --- a/packages/web-app-files/src/components/Search/Preview.vue +++ b/packages/web-app-files/src/components/Search/Preview.vue @@ -12,6 +12,7 @@ :is-path-displayed="true" :is-resource-clickable="false" :parent-folder-link="parentFolderLink" + :parent-folder-link-icon-additional-attributes="parentFolderLinkIconAdditionalAttributes" :parent-folder-name-default="defaultParentFolderName" :is-thumbnail-displayed="displayThumbnails" @parent-folder-clicked="parentFolderClicked" @@ -29,7 +30,7 @@ import { computed, defineComponent, ref, unref } from 'vue' import { mapGetters } from 'vuex' import { createLocationShares, createLocationSpaces } from '../../router' import { basename, dirname } from 'path' -import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables' +import { useCapabilityShareJailEnabled, useGetMatchingSpace } from 'web-pkg/src/composables' import { buildShareSpaceResource, isProjectSpaceResource, Resource } from 'web-client/src/helpers' import { configurationManager } from 'web-pkg/src/configuration' import { eventBus } from 'web-pkg/src/services/eventBus' @@ -53,6 +54,7 @@ export default defineComponent({ } }, setup(props) { + const { getInternalSpace } = useGetMatchingSpace() const previewData = ref() const resource = computed((): Resource => { return { @@ -65,6 +67,7 @@ export default defineComponent({ }) return { ...useFileActions(), + getInternalSpace, hasShareJail: useCapabilityShareJailEnabled(), previewData, resource @@ -142,6 +145,24 @@ export default defineComponent({ return createLocationSpaces('files-spaces-projects') } return this.createFolderLink(dirname(this.resource.path), this.resource.parentFolderId) + }, + + parentFolderLinkIconAdditionalAttributes() { + // Identify if resource is project space or is part of a project space and the resource is located in its root + if ( + isProjectSpaceResource(this.resource) || + (isProjectSpaceResource( + this.getInternalSpace(this.resource.storageId) || ({} as Resource) + ) && + this.resource.path.split('/').length === 2) + ) { + return { + name: 'layout-grid', + 'fill-type': 'fill' + } + } + + return {} } }, mounted() { diff --git a/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue b/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue index 9a34765c994..12884962815 100644 --- a/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue +++ b/packages/web-app-files/src/components/Shares/SharedWithMeSection.vue @@ -29,6 +29,7 @@ :header-position="fileListHeaderY" :sort-by="sortBy" :sort-dir="sortDir" + :grouping-settings="groupingSettings" @file-click="triggerDefaultAction" @row-mounted="rowMounted" @sort="sortHandler" @@ -181,6 +182,14 @@ export default defineComponent({ fileListHeaderY: { type: Number, default: 0 + }, + /** + * This is only relevant for CERN and can be ignored in any other cases. + */ + groupingSettings: { + type: Object, + required: false, + default: null } }, setup() { diff --git a/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue b/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue index c687b592097..6c09803c07b 100644 --- a/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue +++ b/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue @@ -485,8 +485,9 @@ export default defineComponent({ eventBus.publish(SideBarEventTopics.setActivePanel, 'versions') }, getTagLink(tag) { + const currentTerm = unref(this.$router.currentRoute).query?.term return createLocationCommon('files-common-search', { - query: { term: `Tags:"${tag}"`, provider: 'files.sdk' } + query: { provider: 'files.sdk', q_tags: tag, ...(currentTerm && { term: currentTerm }) } }) }, getTagComponentAttrs(tag) { diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue index f2fe4eab03c..83ed3e37325 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/EditDropdown.vue @@ -58,7 +58,7 @@ @click="option.method" > - + diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue index 0d19f0f2b31..e3fe4963125 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/InviteCollaborator/InviteCollaboratorForm.vue @@ -46,19 +46,15 @@ :share-types="selectedCollaborators.map((c) => c.value.shareType)" @option-change="collaboratorExpiryChanged" /> - - - - @@ -93,7 +89,7 @@ import { useStore } from 'web-pkg/src/composables' -import { defineComponent, inject } from 'vue' +import { defineComponent, inject, ref, unref, watch } from 'vue' import { Resource } from 'web-client' import { useShares } from 'web-app-files/src/composables' @@ -126,6 +122,22 @@ export default defineComponent({ setup() { const store = useStore() const clientService = useClientService() + const saving = ref(false) + const savingDelayed = ref(false) + + watch(saving, (newValue) => { + if (!newValue) { + savingDelayed.value = false + return + } + setTimeout(() => { + if (!unref(saving)) { + savingDelayed.value = false + return + } + savingDelayed.value = true + }, 700) + }) return { resource: inject('resource'), hasResharing: useCapabilityFilesSharingResharing(store), @@ -133,6 +145,8 @@ export default defineComponent({ hasShareJail: useCapabilityShareJailEnabled(store), hasRoleCustomPermissions: useCapabilityFilesSharingAllowCustomPermissions(store), clientService, + saving, + savingDelayed, ...useShares() } }, @@ -145,7 +159,6 @@ export default defineComponent({ selectedCollaborators: [], selectedRole: null, customPermissions: null, - saving: false, expirationDate: null, searchQuery: '' } @@ -187,7 +200,7 @@ export default defineComponent({ }, methods: { - ...mapActions(['showMessage']), + ...mapActions(['showMessage', 'showErrorMessage']), ...mapActions('Files', ['addShare']), ...mapActions('runtime/spaces', ['addSpaceMember']), @@ -351,11 +364,11 @@ export default defineComponent({ this.showMessage({ title: this.$gettext('Share was added successfully') }) } errors.forEach((e) => { - this.showMessage({ - title: this.$gettext('Failed to add share for %{displayName}', { + this.showErrorMessage({ + title: this.$gettext('Failed to add share for "%{displayName}"', { displayName: e.displayName }), - status: 'danger' + error: e.error }) }) @@ -379,4 +392,8 @@ export default defineComponent({ .role-selection-dropdown { max-width: 150px; } +#new-collaborators-form-create-button { + padding-left: 30px; + padding-right: 30px; +} diff --git a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue index 1e21f9c10dc..ab679a5b471 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Collaborators/ListItem.vue @@ -373,7 +373,7 @@ export default defineComponent({ } }, methods: { - ...mapActions(['showMessage']), + ...mapActions(['showMessage', 'showErrorMessage']), ...mapActions('Files', ['changeShare']), ...mapActions('runtime/spaces', ['changeSpaceMember']), @@ -394,9 +394,9 @@ export default defineComponent({ this.saveShareChanges({ role, permissions, expirationDate }) } catch (e) { console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to apply new permissions'), - status: 'danger' + error: e }) } }, @@ -408,9 +408,9 @@ export default defineComponent({ this.saveShareChanges({ role, permissions, expirationDate }) } catch (e) { console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to apply expiration date'), - status: 'danger' + error: e }) } }, @@ -437,9 +437,9 @@ export default defineComponent({ this.showMessage({ title: this.$gettext('Share successfully changed') }) } catch (e) { console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Error while editing the share.'), - status: 'danger' + error: e }) } } diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue index 3f8d7c5cc80..0adad067537 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue @@ -346,7 +346,7 @@ export default defineComponent({ }, methods: { ...mapActions('Files', ['addLink', 'updateLink', 'removeLink']), - ...mapActions(['showMessage', 'createModal', 'hideModal']), + ...mapActions(['showMessage', 'showErrorMessage', 'createModal', 'hideModal']), ...mapMutations('Files', ['REMOVE_FILES']), toggleLinkListCollapsed() { @@ -479,9 +479,9 @@ export default defineComponent({ } catch (e) { onError(e) console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to create link'), - status: 'danger' + error: e }) return } @@ -501,9 +501,9 @@ export default defineComponent({ } catch (e) { onError(e) console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to update link'), - status: 'danger' + error: e }) return } @@ -559,9 +559,9 @@ export default defineComponent({ } } catch (e) { console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to delete link'), - status: 'danger' + error: e }) } }, diff --git a/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue b/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue index 3073dcd140f..b344036b24a 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/FileShares.vue @@ -272,7 +272,7 @@ export default defineComponent({ }, methods: { ...mapActions('Files', ['deleteShare', 'addShare']), - ...mapActions(['createModal', 'hideModal', 'showMessage']), + ...mapActions(['createModal', 'hideModal', 'showMessage', 'showErrorMessage']), ...mapMutations('Files', ['REMOVE_FILES']), getDeniedShare(collaborator: Share): Share { @@ -376,9 +376,9 @@ export default defineComponent({ }) } catch (e) { console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to deny access'), - status: 'danger' + error: e }) } } else { @@ -398,9 +398,9 @@ export default defineComponent({ }) } catch (e) { console.error(e) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to grant access'), - status: 'danger' + error: e }) } } @@ -449,9 +449,9 @@ export default defineComponent({ } } catch (error) { console.error(error) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to remove share'), - status: 'danger' + error }) } }, diff --git a/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue b/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue index a7aa31a35e8..22567c7e843 100644 --- a/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue +++ b/packages/web-app-files/src/components/SideBar/Shares/Links/DetailsAndEdit.vue @@ -7,7 +7,10 @@ class="edit-public-link-role-dropdown-toggle oc-text-left" gap-size="none" > - +
{{ formatVersionDate(item) }}{{ formatVersionDateRelative(item) }} - {{ @@ -61,6 +62,7 @@ import { defineComponent, inject, ref, Ref } from 'vue' import { isShareSpaceResource, Resource, SpaceResource } from 'web-client/src/helpers' import { SharePermissions } from 'web-client/src/helpers/share' import { useDownloadFile } from 'web-pkg/src/composables/download/useDownloadFile' +import { formatDateFromJSDate } from 'web-pkg/src/helpers' export default defineComponent({ name: 'FileVersions', @@ -136,12 +138,18 @@ export default defineComponent({ const version = this.currentVersionId(file) return this.downloadFile(this.resource, version) }, - formatVersionDate(file) { + formatVersionDateRelative(file) { return formatRelativeDateFromHTTP( file.fileInfo[DavProperty.LastModifiedDate], this.$language.current ) }, + formatVersionDate(file) { + return formatDateFromJSDate( + new Date(file.fileInfo[DavProperty.LastModifiedDate]), + this.$language.current + ) + }, formatVersionFileSize(file) { return formatFileSize(file.fileInfo[DavProperty.ContentLength], this.$language.current) } diff --git a/packages/web-app-files/src/components/Spaces/SpaceHeader.vue b/packages/web-app-files/src/components/Spaces/SpaceHeader.vue index 737d0b2d85b..0be2c3a62b1 100644 --- a/packages/web-app-files/src/components/Spaces/SpaceHeader.vue +++ b/packages/web-app-files/src/components/Spaces/SpaceHeader.vue @@ -21,7 +21,7 @@
-

{{ space.name }}

+

{{ space.name }}

} = {}) => { return !!unref(appProviders).find((appProvider) => appProvider.enabled) }) + const { openUrl } = useWindowOpen() + const { actions: acceptShareActions } = useFileActionsAcceptShare({ store }) const { actions: copyActions } = useFileActionsCopy({ store }) const { actions: deleteActions } = useFileActionsDelete({ store }) @@ -190,11 +193,8 @@ export const useFileActions = ({ store }: { store?: Store } = {}) => { if (configuration.options.openAppsInTab) { const path = router.resolve(routeOpts).href const target = `${editor.routeName}-${filePath}` - const win = window.open(path, target) - // in case popup is blocked win will be null - if (win) { - win.focus() - } + + openUrl(path, target, true) return } @@ -327,7 +327,7 @@ export const useFileActions = ({ store }: { store?: Store } = {}) => { } as any // TODO: Let users configure whether to open in same/new tab (`_blank` vs `_self`) - window.open(router.resolve(routeOpts).href, '_blank') + openUrl(router.resolve(routeOpts).href, '_blank') } return { diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsAcceptShare.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsAcceptShare.ts index 56a3fa9186c..545c7b33f07 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsAcceptShare.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsAcceptShare.ts @@ -69,13 +69,13 @@ export const useFileActionsAcceptShare = ({ store }: { store?: Store } = {} return } - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $ngettext( 'Failed to accept the selected share.', 'Failed to accept selected shares.', resources.length ), - status: 'danger' + errors }) } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsClearSelection.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsClearSelection.ts deleted file mode 100644 index 4fca10f7668..00000000000 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsClearSelection.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { computed } from 'vue' -import { Store, useStore } from 'vuex' -import { FileAction } from 'web-pkg/src/composables/actions' -import { useGettext } from 'vue3-gettext' - -export const useFileActionsClearSelection = ({ store }: { store?: Store } = {}) => { - store = store || useStore() - const { $gettext } = useGettext() - - const handler = () => { - store.commit('Files/RESET_SELECTION') - } - - const actions = computed((): FileAction[] => [ - { - name: 'clear-selection', - icon: 'close', - label: () => $gettext('Clear selection'), - hideLabel: true, - handler, - isEnabled: ({ resources }) => resources.length > 0, - componentType: 'button', - class: 'oc-files-actions-clear-selection-trigger' - } - ]) - - return { - actions - } -} diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFile.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFile.ts index b733655239e..cbaaabc44c7 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFile.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFile.ts @@ -103,9 +103,9 @@ export const useFileActionsCreateNewFile = ({ }) } catch (error) { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettext('Failed to create file'), - status: 'danger' + error }) } } @@ -150,9 +150,9 @@ export const useFileActionsCreateNewFile = ({ }) } catch (error) { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettext('Failed to create file'), - status: 'danger' + error }) } } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFolder.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFolder.ts index 40d826ec289..8fecc07be41 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFolder.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateNewFolder.ts @@ -1,6 +1,6 @@ import { Resource, SpaceResource } from 'web-client/src/helpers' import { Store } from 'vuex' -import { computed, unref } from 'vue' +import { computed, nextTick, unref } from 'vue' import { useClientService, useRouter, useStore } from 'web-pkg/src/composables' import { FileAction } from 'web-pkg/src/composables/actions' import { useGettext } from 'vue3-gettext' @@ -9,6 +9,7 @@ import { join } from 'path' import { WebDAV } from 'web-client/src/webdav' import { isLocationSpacesActive } from 'web-app-files/src/router' import { getIndicators } from 'web-app-files/src/helpers/statusIndicators' +import { useScrollTo } from '../../scrollTo/useScrollTo' export const useFileActionsCreateNewFolder = ({ store, @@ -17,6 +18,7 @@ export const useFileActionsCreateNewFolder = ({ store = store || useStore() const router = useRouter() const { $gettext } = useGettext() + const { scrollToResource } = useScrollTo() const clientService = useClientService() const currentFolder = computed((): Resource => store.getters['Files/currentFolder']) @@ -72,11 +74,14 @@ export const useFileActionsCreateNewFolder = ({ store.dispatch('showMessage', { title: $gettext('"%{folderName}" was created successfully', { folderName }) }) + + await nextTick() + scrollToResource(resource.id, { forceScroll: true }) } catch (error) { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettext('Failed to create folder'), - status: 'danger' + error }) } } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts index 855c60b8cff..f177e0f43c9 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsCreateSpaceFromResource.ts @@ -47,9 +47,9 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store }) } catch (error) { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettext('Creating space failed…'), - status: 'danger' + error }) } } @@ -94,7 +94,7 @@ export const useFileActionsCreateSpaceFromResource = ({ store }: { store?: Store return [ { name: 'create-space-from-resource', - icon: 'layout-grid', + icon: 'function', handler, label: () => { return $gettext('Create Space from selection') diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsDeclineShare.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsDeclineShare.ts index 65ae1daec96..ae6e9e77cb8 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsDeclineShare.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsDeclineShare.ts @@ -73,13 +73,13 @@ export const useFileActionsDeclineShare = ({ store }: { store?: Store } = { return } - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $ngettext( 'Failed to decline the selected share', 'Failed to decline selected shares', resources.length ), - status: 'danger' + errors }) } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsDelete.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsDelete.ts index a977f0aaa6f..5d1549f04d3 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsDelete.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsDelete.ts @@ -110,8 +110,7 @@ export const useFileActionsDelete = ({ store }: { store?: Store } = {}) => if ( isProjectSpaceResource(space) && - !space.isEditor(store.getters.user) && - !space.isManager(store.getters.user) + !space.canRemoveFromTrashbin({ user: store.getters.user }) ) { return false } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsDownloadArchive.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsDownloadArchive.ts index 7767c439e2b..0fc05aa9cbd 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsDownloadArchive.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsDownloadArchive.ts @@ -56,13 +56,13 @@ export const useFileActionsDownloadArchive = ({ store }: { store?: Store } }) .catch((e) => { console.error(e) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $ngettext( 'Failed to download the selected folder.', // on single selection only available for folders 'Failed to download the selected files.', // on multi selection available for files+folders resources.length ), - status: 'danger' + error: e }) }) } @@ -86,7 +86,9 @@ export const useFileActionsDownloadArchive = ({ store }: { store?: Store } { name: 'download-archive', icon: 'inbox-archive', - handler: (args) => loadingService.addTask(() => handler(args)), + handler: async (args) => { + await loadingService.addTask(() => handler(args)) + }, label: ({ resources }) => { const downloadLabel = $gettext('Download') diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsEmptyTrashBin.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsEmptyTrashBin.ts index 670a89df8cc..1a3ed891981 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsEmptyTrashBin.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsEmptyTrashBin.ts @@ -13,7 +13,7 @@ import { useStore } from 'web-pkg/src/composables' import { useGettext } from 'vue3-gettext' -import { ActionOptions, FileAction, FileActionOptions } from 'web-pkg/src/composables/actions' +import { FileAction, FileActionOptions } from 'web-pkg/src/composables/actions' export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = {}) => { store = store || useStore() @@ -39,12 +39,12 @@ export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = }) .catch((error) => { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $pgettext( 'Error message in case emptying trash bin fails', 'Failed to empty trash bin' ), - status: 'danger' + error }) }) .finally(() => { @@ -85,19 +85,20 @@ export const useFileActionsEmptyTrashBin = ({ store }: { store?: Store } = if ( isProjectSpaceResource(space) && - !space.isEditor(store.getters.user) && - !space.isManager(store.getters.user) + !space.canRemoveFromTrashbin({ user: store.getters.user }) ) { return false } - // empty trash bin is not available for individual resources, but only for the trash bin as a whole - return resources.length === 0 + return true + }, + isDisabled: () => { + return store.getters['Files/activeFiles'].length === 0 }, - isDisabled: ({ resources }: ActionOptions) => store.getters['Files/activeFiles'].length === 0, componentType: 'button', class: 'oc-files-actions-empty-trash-bin-trigger', - variation: 'danger' + variation: 'danger', + appearance: 'filled' } ]) diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsFavorite.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsFavorite.ts index 75a6c7b7db8..320c14f6f42 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsFavorite.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsFavorite.ts @@ -25,12 +25,12 @@ export const useFileActionsFavorite = ({ store }: { store?: Store } = {}) = client: clientService.owncloudSdk, file: resources[0] }) - .catch(() => { + .catch((error) => { const translated = $gettext('Failed to change favorite state of "%{file}"') const title = $gettextInterpolate(translated, { file: resources[0].name }, true) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: title, - status: 'danger' + error }) }) } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsImport.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsImport.ts deleted file mode 100644 index 7623e10256b..00000000000 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsImport.ts +++ /dev/null @@ -1,129 +0,0 @@ -import Dashboard from '@uppy/dashboard' -import OneDrive from '@uppy/onedrive' -import { Store } from 'vuex' -import { computed, unref } from 'vue' -import { useConfigurationManager, useRouter } from 'web-pkg/src/composables' -import { useGettext } from 'vue3-gettext' -import { Resource } from 'web-client/src/helpers' -import { useAccessToken, useStore } from 'web-pkg/src/composables' -import { FileAction } from 'web-pkg/src/composables/actions' -import { useService } from 'web-pkg/src/composables/service' -import { UppyService } from 'web-runtime/src/services/uppyService' -import { isLocationPublicActive } from 'web-app-files/src/router' -import { ConfigurationManager } from 'web-pkg/types' - -export const useFileActionsImport = ({ - store, - configurationManager -}: { - store?: Store - configurationManager?: ConfigurationManager -} = {}) => { - store = store || useStore() - configurationManager = configurationManager || useConfigurationManager() - - const router = useRouter() - const { $gettext } = useGettext() - const accessToken = useAccessToken({ store }) - const uppyService = useService('$uppyService') - - const supportedClouds = ['OneDrive'] - - const currentFolder = computed(() => { - return store.getters['Files/currentFolder'] - }) - const canUpload = computed(() => { - return unref(currentFolder)?.canUpload({ user: store.getters.user }) - }) - - const removeUppyPlugins = () => { - const dashboardPlugin = uppyService.getPlugin('Dashboard') - if (dashboardPlugin) { - uppyService.removePlugin(dashboardPlugin) - } - for (const cloud of supportedClouds) { - const onedrivePlugin = uppyService.getPlugin(cloud) - if (onedrivePlugin) { - uppyService.removePlugin(onedrivePlugin) - } - } - } - - uppyService.subscribe('addedForUpload', () => { - return store.dispatch('hideModal') - }) - - uppyService.subscribe('uploadCompleted', () => { - removeUppyPlugins() - const tusPlugin = uppyService.getPlugin('Tus') - if (tusPlugin) { - tusPlugin.setOptions({ headers: {} }) - } - }) - - const handler = async () => { - const tusPlugin = uppyService.getPlugin('Tus') - if (tusPlugin) { - tusPlugin.setOptions({ - headers: { Authorization: 'Bearer ' + unref(accessToken) } - }) - } - - const modal = { - variation: 'passive', - title: $gettext('Import files'), - cancelText: $gettext('Cancel'), - withoutButtonConfirm: true, - onCancel: () => { - removeUppyPlugins() - return store.dispatch('hideModal') - } - } - - await store.dispatch('createModal', modal) - - uppyService.addPlugin(Dashboard, { - uppyService, - inline: true, - target: '.oc-modal-body', - disableLocalFiles: true, - locale: { - strings: { - cancel: $gettext('Cancel'), - importFiles: $gettext('Import files from:'), - importFrom: $gettext('Import from %{name}') - } - } - }) - uppyService.addPlugin(OneDrive, { - target: Dashboard, - companionUrl: configurationManager.options.upload.companionUrl - }) - } - - const actions = computed((): FileAction[] => { - return [ - { - name: 'import-files', - icon: 'cloud', - handler, - label: () => $gettext('Import'), - isEnabled: () => { - if (!configurationManager.options.upload.companionUrl) { - return false - } - if (isLocationPublicActive(router, 'files-public-link')) { - return false - } - return unref(canUpload) - }, - componentType: 'button', - class: 'oc-files-actions-import' - } - ] - }) - - return { - actions - } -} diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsPaste.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsPaste.ts index a0cd440299e..49f167012b1 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsPaste.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsPaste.ts @@ -64,6 +64,7 @@ export const useFileActionsPaste = ({ store }: { store?: Store } = {}) => { createModal: (...args) => store.dispatch('createModal', ...args), hideModal: (...args) => store.dispatch('hideModal', ...args), showMessage: (...args) => store.dispatch('showMessage', ...args), + showErrorMessage: (...args) => store.dispatch('showErrorMessage', ...args), $gettext, $gettextInterpolate, $ngettext diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsRename.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsRename.ts index ee7bba8b3d8..56e35637342 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsRename.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsRename.ts @@ -143,9 +143,9 @@ export const useFileActionsRename = ({ store }: { store?: Store } = {}) => translated = $gettext('Failed to rename "%{file}" to "%{newName}" - the file is locked') } const title = $gettextInterpolate(translated, { file: resource.name, newName }, true) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title, - status: 'danger' + error }) } } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsRestore.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsRestore.ts index 14db06b8cef..59e2e52a6c3 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsRestore.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsRestore.ts @@ -97,6 +97,7 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => (...args) => store.dispatch('createModal', ...args), (...args) => store.dispatch('hideModal', ...args), (...args) => store.dispatch('showMessage', ...args), + (...args) => store.dispatch('showErrorMessage', ...args), $gettext, $ngettext, $gettextInterpolate @@ -157,6 +158,7 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => ) => { const restoredResources = [] const failedResources = [] + const errors = [] let createdFolderPaths = [] for (const [i, resource] of resources.entries()) { @@ -173,6 +175,7 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => restoredResources.push(resource) } catch (e) { console.error(e) + errors.push(e) failedResources.push(resource) } finally { setProgress({ total: resources.length, current: i + 1 }) @@ -207,9 +210,9 @@ export const useFileActionsRestore = ({ store }: { store?: Store } = {}) => translated = $gettext('Failed to restore %{resourceCount} files') translateParams.resourceCount = failedResources.length } - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettextInterpolate(translated, translateParams, true), - status: 'danger' + errors }) } diff --git a/packages/web-app-files/src/composables/actions/files/useFileActionsSetImage.ts b/packages/web-app-files/src/composables/actions/files/useFileActionsSetImage.ts index e2063100794..32cf96151bb 100644 --- a/packages/web-app-files/src/composables/actions/files/useFileActionsSetImage.ts +++ b/packages/web-app-files/src/composables/actions/files/useFileActionsSetImage.ts @@ -73,9 +73,9 @@ export const useFileActionsSetImage = ({ store }: { store?: Store } = {}) = }) } catch (error) { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettext('Failed to set space image'), - status: 'danger' + error }) } } diff --git a/packages/web-app-files/src/composables/actions/helpers/useFileActionsDeleteResources.ts b/packages/web-app-files/src/composables/actions/helpers/useFileActionsDeleteResources.ts index 4e0ae7d4b94..63b7d3516e4 100644 --- a/packages/web-app-files/src/composables/actions/helpers/useFileActionsDeleteResources.ts +++ b/packages/web-app-files/src/composables/actions/helpers/useFileActionsDeleteResources.ts @@ -147,9 +147,9 @@ export const useFileActionsDeleteResources = ({ store }: { store?: Store }) console.error(error) const translated = $gettext('Failed to delete "%{file}"') - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettextInterpolate(translated, { file: resource.name }, true), - status: 'danger' + error }) }) } diff --git a/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsUploadImage.ts b/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsUploadImage.ts index 6f47210376b..a8bf29ea2ec 100644 --- a/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsUploadImage.ts +++ b/packages/web-app-files/src/composables/actions/spaces/useSpaceActionsUploadImage.ts @@ -44,9 +44,8 @@ export const useSpaceActionsUploadImage = ({ } if (!previewService.isMimetypeSupported(file.type, true)) { - return store.dispatch('showMessage', { - title: $gettext('The file type is unsupported'), - status: 'danger' + return store.dispatch('showErrorMessage', { + title: $gettext('The file type is unsupported') }) } @@ -93,9 +92,9 @@ export const useSpaceActionsUploadImage = ({ }) .catch((error) => { console.error(error) - store.dispatch('showMessage', { + store.dispatch('showErrorMessage', { title: $gettext('Failed to upload space image'), - status: 'danger' + error }) }) }) diff --git a/packages/web-app-files/src/composables/keyboardActions/index.ts b/packages/web-app-files/src/composables/keyboardActions/index.ts new file mode 100644 index 00000000000..b61746f8681 --- /dev/null +++ b/packages/web-app-files/src/composables/keyboardActions/index.ts @@ -0,0 +1,4 @@ +export * from './useKeyboardTableNavigation' +export * from './useKeyboardTableMouseActions' +export * from './useKeyboardTableActions' +export * from './useKeyboardTableSpaceActions' diff --git a/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableActions.ts b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableActions.ts new file mode 100644 index 00000000000..91e1b5364be --- /dev/null +++ b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableActions.ts @@ -0,0 +1,15 @@ +import { Key, KeyboardActions, ModifierKey } from 'web-pkg/src/composables/keyboardActions' +import { useStore } from 'web-pkg' +import { useGettext } from 'vue3-gettext' + +export const useKeyboardTableActions = (keyActions: KeyboardActions) => { + const store = useStore() + const language = useGettext() + + keyActions.bindKeyAction({ modifier: ModifierKey.Ctrl, primary: Key.C }, () => { + store.dispatch('Files/copySelectedFiles', { + ...language, + resources: store.getters['Files/selectedFiles'] + }) + }) +} diff --git a/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableMouseActions.ts b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableMouseActions.ts new file mode 100644 index 00000000000..532ec1f4268 --- /dev/null +++ b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableMouseActions.ts @@ -0,0 +1,63 @@ +import { onBeforeUnmount, onMounted, unref, computed } from 'vue' +import { eventBus, useStore } from 'web-pkg' +import { KeyboardActions } from 'web-pkg/src/composables/keyboardActions' +import { Resource } from 'web-client' + +export const useKeyboardTableMouseActions = (keyActions: KeyboardActions) => { + const store = useStore() + const latestSelectedId = computed(() => store.state.Files.latestSelectedId) + + let fileListClickedEvent + let fileListClickedMetaEvent + let fileListClickedShiftEvent + + const handleCtrlClickAction = (resource: Resource) => { + store.dispatch('Files/toggleFileSelection', { id: resource.id }) + } + + const handleShiftClickAction = ({ resource, skipTargetSelection }) => { + const parent = document.querySelectorAll(`[data-item-id='${resource.id}']`)[0] + const resourceNodes = Object.values(parent.parentNode.children) + const latestNode = resourceNodes.find( + (r) => r.getAttribute('data-item-id') === unref(latestSelectedId) + ) + const clickedNode = resourceNodes.find((r) => r.getAttribute('data-item-id') === resource.id) + + let latestNodeIndex = resourceNodes.indexOf(latestNode) + latestNodeIndex = latestNodeIndex === -1 ? 0 : latestNodeIndex + + const clickedNodeIndex = resourceNodes.indexOf(clickedNode) + const minIndex = Math.min(latestNodeIndex, clickedNodeIndex) + const maxIndex = Math.max(latestNodeIndex, clickedNodeIndex) + + for (let i = minIndex; i <= maxIndex; i++) { + const nodeId = resourceNodes[i].getAttribute('data-item-id') + if (skipTargetSelection && nodeId === resource.id) { + continue + } + store.commit('Files/ADD_FILE_SELECTION', { id: nodeId }) + } + store.commit('Files/SET_LATEST_SELECTED_FILE_ID', resource.id) + } + + onMounted(() => { + fileListClickedEvent = eventBus.subscribe( + 'app.files.list.clicked', + keyActions.resetSelectionCursor + ) + fileListClickedMetaEvent = eventBus.subscribe( + 'app.files.list.clicked.meta', + handleCtrlClickAction + ) + fileListClickedShiftEvent = eventBus.subscribe( + 'app.files.list.clicked.shift', + handleShiftClickAction + ) + }) + + onBeforeUnmount(() => { + eventBus.unsubscribe('app.files.list.clicked', fileListClickedEvent) + eventBus.unsubscribe('app.files.list.clicked.meta', fileListClickedMetaEvent) + eventBus.unsubscribe('app.files.list.clicked.shift', fileListClickedShiftEvent) + }) +} diff --git a/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableNavigation.ts b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableNavigation.ts new file mode 100644 index 00000000000..548a1cc853b --- /dev/null +++ b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableNavigation.ts @@ -0,0 +1,108 @@ +import { useStore } from 'web-pkg' +import { useScrollTo } from 'web-app-files/src/composables/scrollTo' +import { computed, Ref, unref, nextTick } from 'vue' +import { Key, KeyboardActions, ModifierKey } from 'web-pkg/src/composables/keyboardActions' +import { Resource } from 'web-client' + +export const useKeyboardTableNavigation = ( + keyActions: KeyboardActions, + paginatedResources: Ref +) => { + const store = useStore() + const { scrollToResource } = useScrollTo() + const latestSelectedId = computed(() => store.state.Files.latestSelectedId) + + keyActions.bindKeyAction({ primary: Key.ArrowUp }, () => handleNavigateAction(true)) + + keyActions.bindKeyAction({ primary: Key.ArrowDown }, () => handleNavigateAction()) + + keyActions.bindKeyAction({ modifier: ModifierKey.Shift, primary: Key.ArrowUp }, () => + handleShiftUpAction() + ) + + keyActions.bindKeyAction({ modifier: ModifierKey.Shift, primary: Key.ArrowDown }, () => + handleShiftDownAction() + ) + + keyActions.bindKeyAction({ modifier: ModifierKey.Ctrl, primary: Key.A }, () => + handleSelectAllAction() + ) + + keyActions.bindKeyAction({ primary: Key.Space }, () => { + store.dispatch('Files/toggleFileSelection', { id: unref(latestSelectedId) }) + }) + + keyActions.bindKeyAction({ primary: Key.Esc }, () => { + keyActions.resetSelectionCursor() + store.dispatch('Files/resetFileSelection') + }) + + const handleNavigateAction = async (up = false) => { + const nextId = !unref(latestSelectedId) ? getFirstResourceId() : getNextResourceId(up) + if (nextId === -1) { + return + } + keyActions.resetSelectionCursor() + store.dispatch('Files/resetFileSelection') + await nextTick() + store.commit('Files/ADD_FILE_SELECTION', { id: nextId }) + await nextTick() + scrollToResource(nextId) + } + + const getNextResourceId = (previous = false) => { + const latestSelectedResourceIndex = paginatedResources.value.findIndex( + (resource) => resource.id === latestSelectedId.value + ) + if (latestSelectedResourceIndex === -1) { + return -1 + } + const nextResourceIndex = latestSelectedResourceIndex + (previous ? -1 : 1) + if (nextResourceIndex < 0 || nextResourceIndex >= paginatedResources.value.length) { + return -1 + } + return paginatedResources.value[nextResourceIndex].id + } + + const getFirstResourceId = () => { + return paginatedResources.value.length ? paginatedResources.value[0].id : -1 + } + + const handleSelectAllAction = () => { + keyActions.resetSelectionCursor() + store.commit('Files/SET_FILE_SELECTION', paginatedResources.value) + } + + const handleShiftUpAction = async () => { + const nextResourceId = getNextResourceId(true) + if (nextResourceId === -1) { + return + } + if (unref(keyActions.selectionCursor) > 0) { + // deselect + await store.dispatch('Files/toggleFileSelection', { id: unref(latestSelectedId) }) + store.commit('Files/SET_LATEST_SELECTED_FILE_ID', nextResourceId) + } else { + // select + store.commit('Files/ADD_FILE_SELECTION', { id: nextResourceId }) + } + scrollToResource(nextResourceId) + keyActions.selectionCursor.value = unref(keyActions.selectionCursor) - 1 + } + const handleShiftDownAction = () => { + const nextResourceId = getNextResourceId() + if (nextResourceId === -1) { + return + } + if (unref(keyActions.selectionCursor) < 0) { + // deselect + store.dispatch('Files/toggleFileSelection', { id: unref(latestSelectedId) }) + store.commit('Files/SET_LATEST_SELECTED_FILE_ID', nextResourceId) + } else { + // select + store.commit('Files/ADD_FILE_SELECTION', { id: nextResourceId }) + } + scrollToResource(nextResourceId) + keyActions.selectionCursor.value = unref(keyActions.selectionCursor) + 1 + } +} diff --git a/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableSpaceActions.ts b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableSpaceActions.ts new file mode 100644 index 00000000000..b84e3ba8674 --- /dev/null +++ b/packages/web-app-files/src/composables/keyboardActions/useKeyboardTableSpaceActions.ts @@ -0,0 +1,34 @@ +import { Key, KeyboardActions, ModifierKey } from 'web-pkg/src/composables/keyboardActions' +import { SpaceResource } from 'web-client' +import { useStore } from 'web-pkg' +import { useGettext } from 'vue3-gettext' +import { unref } from 'vue' +import { useFileActionsPaste } from 'web-app-files/src/composables' + +export const useKeyboardTableSpaceActions = (keyActions: KeyboardActions, space: SpaceResource) => { + const store = useStore() + const language = useGettext() + + const { actions: pasteFileActions } = useFileActionsPaste({ store }) + const pasteFileAction = unref(pasteFileActions)[0].handler + + keyActions.bindKeyAction({ modifier: ModifierKey.Ctrl, primary: Key.C }, () => { + store.dispatch('Files/copySelectedFiles', { + ...language, + space: space, + resources: store.getters['Files/selectedFiles'] + }) + }) + + keyActions.bindKeyAction({ modifier: ModifierKey.Ctrl, primary: Key.V }, () => { + pasteFileAction({ space: space }) + }) + + keyActions.bindKeyAction({ modifier: ModifierKey.Ctrl, primary: Key.X }, () => { + store.dispatch('Files/cutSelectedFiles', { + ...language, + space: space, + resources: store.getters['Files/selectedFiles'] + }) + }) +} diff --git a/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts b/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts index da03eaf25c0..2a683e5312d 100644 --- a/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts +++ b/packages/web-app-files/src/composables/resourcesViewDefaults/useResourcesViewDefaults.ts @@ -1,7 +1,7 @@ import { nextTick, computed, unref, Ref } from 'vue' import { folderService } from '../../services/folder' import { fileList } from '../../helpers/ui' -import { usePagination, useSort, SortDir, SortField } from 'web-pkg/src/composables' +import { usePagination, useSort, SortDir, SortField, useRouteName } from 'web-pkg/src/composables' import { useSideBar } from 'web-pkg/src/composables/sideBar' import { @@ -66,7 +66,11 @@ export const useResourcesViewDefaults = ( const storeItems = computed((): T[] => store.getters['Files/activeFiles'] || []) - const currentViewModeQuery = useRouteQuery('view-mode', ViewModeConstants.defaultModeName) + const currentRoute = useRouteName() + const currentViewModeQuery = useRouteQuery( + `${unref(currentRoute)}-view-mode`, + ViewModeConstants.defaultModeName + ) const currentViewMode = computed((): string => queryItemAsString(currentViewModeQuery.value)) const viewMode = useViewMode(currentViewMode) diff --git a/packages/web-app-files/src/composables/scrollTo/useScrollTo.ts b/packages/web-app-files/src/composables/scrollTo/useScrollTo.ts index ad87357bf1c..9362c44f462 100644 --- a/packages/web-app-files/src/composables/scrollTo/useScrollTo.ts +++ b/packages/web-app-files/src/composables/scrollTo/useScrollTo.ts @@ -1,12 +1,13 @@ import { computed, unref } from 'vue' import { Resource } from 'web-client/src' -import { eventBus, useStore } from 'web-pkg/src' -import { queryItemAsString } from 'web-pkg/src/composables/appDefaults' +import { queryItemAsString } from 'web-pkg/src/composables/appDefaults/useAppNavigation' +import { useStore } from 'web-pkg/src/composables/store/useStore' +import { eventBus } from 'web-pkg/src/services' import { useRouteQuery } from 'web-pkg/src/composables' import { SideBarEventTopics } from 'web-pkg/src/composables/sideBar' export interface ScrollToResult { - scrollToResource(resource: Resource): void + scrollToResource(resourceId: Resource['id'], options?: { forceScroll?: boolean }): void scrollToResourceFromRoute(resources: Resource[]): void } @@ -21,29 +22,25 @@ export const useScrollTo = (): ScrollToResult => { return queryItemAsString(unref(detailsQuery)) }) - const scrollToResource = (resource) => { + const scrollToResource = (resourceId: Resource['id'], options = { forceScroll: false }) => { const resourceElement = document.querySelectorAll( - `[data-item-id='${resource.id}']` + `[data-item-id='${resourceId}']` )[0] as HTMLElement if (!resourceElement) { return } - // bottom reached - if (resourceElement.getBoundingClientRect().bottom > window.innerHeight) { - resourceElement.scrollIntoView(false) - return - } - - const topbarElement = document.getElementsByClassName('files-topbar')[0] as HTMLElement + const topbarElement = document.getElementById('files-app-bar') // topbar height + th height + height of one row = offset needed when scrolling top const topOffset = topbarElement.offsetHeight + resourceElement.offsetHeight * 2 - // top reached - if (resourceElement.getBoundingClientRect().top < topOffset) { - const fileListWrapperElement = document.getElementsByClassName('files-view-wrapper')[0] - fileListWrapperElement.scrollBy(0, -resourceElement.offsetHeight) + if ( + resourceElement.getBoundingClientRect().bottom > window.innerHeight || + resourceElement.getBoundingClientRect().top < topOffset || + options.forceScroll + ) { + resourceElement.scrollIntoView({ behavior: 'smooth', block: 'center' }) } } @@ -55,7 +52,7 @@ export const useScrollTo = (): ScrollToResult => { const resource = unref(resources).find((r) => r.id === unref(scrollTo)) if (resource) { store.commit('Files/SET_FILE_SELECTION', [resource]) - scrollToResource(resource) + scrollToResource(resource.id, { forceScroll: true }) if (unref(details)) { eventBus.publish(SideBarEventTopics.openWithPanel, unref(details)) diff --git a/packages/web-app-files/src/helpers/resource/actions/transfer.ts b/packages/web-app-files/src/helpers/resource/actions/transfer.ts index 505001e5bca..62785c0da0d 100644 --- a/packages/web-app-files/src/helpers/resource/actions/transfer.ts +++ b/packages/web-app-files/src/helpers/resource/actions/transfer.ts @@ -22,11 +22,20 @@ export class ResourceTransfer extends ConflictDialog { createModal: (modal: object) => void, hideModal: () => void, showMessage: (data: object) => void, + showErrorMessage: (data: object) => void, $gettext: (msg: string) => string, $ngettext: (msgid: string, plural: string, n: number) => string, $gettextInterpolate: (msg: string, context: object, disableHtmlEscaping?: boolean) => string ) { - super(createModal, hideModal, showMessage, $gettext, $ngettext, $gettextInterpolate) + super( + createModal, + hideModal, + showMessage, + showErrorMessage, + $gettext, + $ngettext, + $gettextInterpolate + ) } hasRecursion(): boolean { @@ -45,7 +54,7 @@ export class ResourceTransfer extends ConflictDialog { "You can't paste the selected files at this location because you can't paste an item into itself.", count ) - this.showMessage({ title, status: 'danger' }) + this.showErrorMessage({ title }) } showResultMessage(errors, movedResources: Array, transferType: TransferType) { @@ -89,9 +98,9 @@ export class ResourceTransfer extends ConflictDialog { true ) } - this.showMessage({ + this.showErrorMessage({ title, - status: 'danger' + errors }) } diff --git a/packages/web-app-files/src/helpers/resource/actions/upload.ts b/packages/web-app-files/src/helpers/resource/actions/upload.ts index 4bd36001fd2..3f15c2894b1 100644 --- a/packages/web-app-files/src/helpers/resource/actions/upload.ts +++ b/packages/web-app-files/src/helpers/resource/actions/upload.ts @@ -19,6 +19,7 @@ export class ResourceConflict extends ConflictDialog { (modal) => store.dispatch('createModal', modal), () => store.dispatch('hideModal'), (msg) => store.dispatch('showMessage', msg), + (msg) => store.dispatch('showErrorMessage', msg), $gettext, $ngettext, $gettextInterpolate @@ -140,6 +141,12 @@ export class ResourceConflict extends ConflictDialog { const extension = extractExtensionFromFile({ name: fileName } as Resource) file.name = resolveFileNameDuplicate(fileName, extension, this.files) file.meta.name = file.name + if (file.xhrUpload?.endpoint) { + file.xhrUpload.endpoint = file.xhrUpload.endpoint.replace( + new RegExp(`/${encodeURIComponent(fileName)}`), + `/${encodeURIComponent(file.name)}` + ) + } } for (const folder of foldersToKeepBoth) { const filesInFolder = files.filter((e) => e.meta.relativeFolder.split('/')[1] === folder) @@ -153,9 +160,16 @@ export class ResourceConflict extends ConflictDialog { new RegExp(`/${folder}/`), `/${newFolderName}/` ) - file.meta.tusEndpoint = encodeURI( - file.meta.tusEndpoint.replace(new RegExp(`/${folder}`), `/${newFolderName}`) + file.meta.tusEndpoint = file.meta.tusEndpoint.replace( + new RegExp(`/${encodeURIComponent(folder)}`), + `/${encodeURIComponent(newFolderName)}` ) + if (file.xhrUpload?.endpoint) { + file.xhrUpload.endpoint = file.xhrUpload.endpoint.replace( + new RegExp(`/${encodeURIComponent(folder)}`), + `/${encodeURIComponent(newFolderName)}` + ) + } } } return files diff --git a/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts b/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts index 396d39b4c98..a08e9b9bcd4 100644 --- a/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts +++ b/packages/web-app-files/src/helpers/resource/conflictHandling/conflictDialog.ts @@ -13,6 +13,7 @@ export class ConflictDialog { protected createModal: (modal: object) => void, protected hideModal: () => void, protected showMessage: (data: object) => void, + protected showErrorMessage: (data: object) => void, protected $gettext: (msg: string) => string, protected $ngettext: (msgid: string, plural: string, n: number) => string, protected $gettextInterpolate: ( @@ -132,8 +133,11 @@ export class ConflictDialog { const modal = { variation: 'danger', title: this.$gettext('Copy here?'), - customContent: - '

Moving files from one space to another is not possible. Do you want to copy instead?

Note: Links and shares of the original file are not copied.

', + customContent: `

${this.$gettext( + 'Moving files from one space to another is not possible. Do you want to copy instead?' + )}

${this.$gettext( + 'Note: Links and shares of the original file are not copied.' + )}

`, cancelText: this.$gettext('Cancel'), confirmText: this.$gettext('Copy here'), onCancel: () => { diff --git a/packages/web-app-files/src/helpers/resources.ts b/packages/web-app-files/src/helpers/resources.ts index 6ba5053aae5..b270d5b115a 100644 --- a/packages/web-app-files/src/helpers/resources.ts +++ b/packages/web-app-files/src/helpers/resources.ts @@ -89,6 +89,9 @@ function addSharedWithToShares(shares) { previousShare?.file_source === share.file_source ) { if (ShareTypes.containsAnyValue(ShareTypes.authenticated, [parseInt(share.share_type)])) { + if (share.stime > previousShare.stime) { + previousShare.stime = share.stime + } previousShare.sharedWith.push({ username: share.share_with, name: share.share_with_displayname, diff --git a/packages/web-app-files/src/helpers/share/link.ts b/packages/web-app-files/src/helpers/share/link.ts index 69705cf04d1..6f912eca3f9 100644 --- a/packages/web-app-files/src/helpers/share/link.ts +++ b/packages/web-app-files/src/helpers/share/link.ts @@ -68,19 +68,26 @@ export const createQuicklink = async (args: CreateQuicklink): Promise => params.spaceRef = resource.fileId || resource.id - const link = await store.dispatch('Files/addLink', { - path: resource.path, - client: clientService.owncloudSdk, - params, - storageId: resource.fileId || resource.id - }) + try { + const link = await store.dispatch('Files/addLink', { + path: resource.path, + client: clientService.owncloudSdk, + params, + storageId: resource.fileId || resource.id + }) + const { copy } = useClipboard({ legacy: true }) + copy(link.url) - const { copy } = useClipboard({ legacy: true }) - copy(link.url) + await store.dispatch('showMessage', { + title: $gettext('The link has been copied to your clipboard.') + }) - await store.dispatch('showMessage', { - title: $gettext('The link has been copied to your clipboard.') - }) - - return link + return link + } catch (e) { + console.error(e) + await store.dispatch('showErrorMessage', { + title: $gettext('Copy link failed'), + error: e + }) + } } diff --git a/packages/web-app-files/src/helpers/share/triggerShareAction.ts b/packages/web-app-files/src/helpers/share/triggerShareAction.ts index 47f706df7c9..be537a83e28 100644 --- a/packages/web-app-files/src/helpers/share/triggerShareAction.ts +++ b/packages/web-app-files/src/helpers/share/triggerShareAction.ts @@ -1,5 +1,6 @@ import { aggregateResourceShares } from '../resources' import { ShareStatus } from 'web-client/src/helpers/share/status' +import { HttpError } from 'web-pkg/src/errors' export async function triggerShareAction(resource, status, hasReSharing, hasShareJail, $client) { const method = _getRequestMethod(status) @@ -16,7 +17,7 @@ export async function triggerShareAction(resource, status, hasReSharing, hasShar // exit on failure if (response.status !== 200) { - throw new Error(response.statusText) + throw new HttpError(response.statusText, response) } // get updated share from response and transform & return it diff --git a/packages/web-app-files/src/index.ts b/packages/web-app-files/src/index.ts index d984208118a..f671c1b9b45 100644 --- a/packages/web-app-files/src/index.ts +++ b/packages/web-app-files/src/index.ts @@ -93,7 +93,7 @@ const navItems = (context): AppNavigationItem[] => { }, activeFor: [{ path: `/${appInfo.id}/trash` }], enabled(capabilities) { - return capabilities.dav?.trashbin === '1.0' + return capabilities.dav?.trashbin === '1.0' && capabilities.files?.undelete } } ] diff --git a/packages/web-app-files/src/quickActions.ts b/packages/web-app-files/src/quickActions.ts index 8488721e40f..a2e01d0d08a 100644 --- a/packages/web-app-files/src/quickActions.ts +++ b/packages/web-app-files/src/quickActions.ts @@ -31,9 +31,8 @@ export function showQuickLinkPasswordModal({ $gettext, store }, onConfirm) { onCancel: () => store.dispatch('hideModal'), onConfirm: async (password) => { if (!password || password.trim() === '') { - store.dispatch('showMessage', { - title: $gettext('Password cannot be empty'), - status: 'danger' + store.dispatch('showErrorMessage', { + title: $gettext('Password cannot be empty') }) } else { await store.dispatch('hideModal') @@ -104,4 +103,4 @@ export default { }, displayed: canShare } -} +} // FIXME: fix type, then add: satisfies ApplicationQuickActions diff --git a/packages/web-app-files/src/router/common.ts b/packages/web-app-files/src/router/common.ts index b5c13aed739..b0e18e25daa 100644 --- a/packages/web-app-files/src/router/common.ts +++ b/packages/web-app-files/src/router/common.ts @@ -27,7 +27,7 @@ export const buildRoutes = (components: RouteComponents): RouteRecordRaw[] => [ meta: { authContext: 'user', title: $gettext('Search results'), - contextQueryItems: ['term', 'provider', 'q_tags'] + contextQueryItems: ['term', 'provider', 'q_tags', 'q_fullText', 'scope', 'useScope'] } } ] diff --git a/packages/web-app-files/src/search/sdk/list.ts b/packages/web-app-files/src/search/sdk/list.ts index 6e467f0b21c..b93aa166194 100644 --- a/packages/web-app-files/src/search/sdk/list.ts +++ b/packages/web-app-files/src/search/sdk/list.ts @@ -37,6 +37,8 @@ export default class List implements SearchList { } async search(term: string): Promise { + const useSpacesEndpoint = this.store.getters.capabilities?.spaces?.enabled === true + if (!term) { return { totalResults: null, @@ -47,7 +49,8 @@ export default class List implements SearchList { const { range, results } = await this.clientService.owncloudSdk.files.search( term, searchLimit, - DavProperties.Default + DavProperties.Default, + useSpacesEndpoint ) return { diff --git a/packages/web-app-files/src/search/sdk/preview.ts b/packages/web-app-files/src/search/sdk/preview.ts index bf97b61c284..354f7d0b36c 100644 --- a/packages/web-app-files/src/search/sdk/preview.ts +++ b/packages/web-app-files/src/search/sdk/preview.ts @@ -47,10 +47,13 @@ export default class Preview implements SearchPreview { } const areHiddenFilesShown = this.store.state.Files?.areHiddenFilesShown + const useSpacesEndpoint = this.store.getters.capabilities?.spaces?.enabled === true + const { range, results } = await this.clientService.owncloudSdk.files.search( term, previewSearchLimit, // todo: add configuration option, other places need that too... needs consolidation - DavProperties.Default + DavProperties.Default, + useSpacesEndpoint ) const resources = results.reduce((acc, result) => { const matchingSpace = this.getMatchingSpace(result.fileInfo[DavProperty.FileParent]) diff --git a/packages/web-app-files/src/services/folder/loaderSpace.ts b/packages/web-app-files/src/services/folder/loaderSpace.ts index 42d88a664a3..cdbe88c9d21 100644 --- a/packages/web-app-files/src/services/folder/loaderSpace.ts +++ b/packages/web-app-files/src/services/folder/loaderSpace.ts @@ -48,7 +48,10 @@ export class FolderLoaderSpace implements FolderLoader { path, fileId }) - replaceInvalidFileRoute({ space, resource: currentFolder, path, fileId }) + // if current folder has no id (= singe file public link) we must not correct the route + if (currentFolder.id) { + replaceInvalidFileRoute({ space, resource: currentFolder, path, fileId }) + } if (path === '/') { if (space.driveType === 'share') { diff --git a/packages/web-app-files/src/store/actions.ts b/packages/web-app-files/src/store/actions.ts index aae24fb4ddf..41a74c02b6d 100644 --- a/packages/web-app-files/src/store/actions.ts +++ b/packages/web-app-files/src/store/actions.ts @@ -81,6 +81,7 @@ export default { createModal, hideModal, showMessage, + showErrorMessage, $gettext, $gettextInterpolate, $ngettext, @@ -98,6 +99,7 @@ export default { createModal, hideModal, showMessage, + showErrorMessage, $gettext, $ngettext, $gettextInterpolate @@ -200,10 +202,10 @@ export default { } const title = $gettextInterpolate(translated, { file: file.name }, true) context.dispatch( - 'showMessage', + 'showErrorMessage', { title: title, - status: 'danger' + error }, { root: true } ) diff --git a/packages/web-app-files/src/views/FilesDrop.vue b/packages/web-app-files/src/views/FilesDrop.vue index 200a41cb342..ffd8948a1b5 100644 --- a/packages/web-app-files/src/views/FilesDrop.vue +++ b/packages/web-app-files/src/views/FilesDrop.vue @@ -1,15 +1,14 @@ @@ -75,6 +77,7 @@ import FilesViewWrapper from '../../components/FilesViewWrapper.vue' import { buildShareSpaceResource } from 'web-client/src/helpers' import { configurationManager } from 'web-pkg/src/configuration' import { useCapabilityShareJailEnabled, useSort, useStore } from 'web-pkg/src/composables' +import { useGroupingSettings } from 'web-pkg/src/cern/composables' export default defineComponent({ components: { @@ -193,7 +196,10 @@ export default defineComponent({ declinedHandleSort, declinedSortBy, declinedSortDir, - declinedItems + declinedItems, + + // CERN + ...useGroupingSettings({ sortBy: acceptedSortBy, sortDir: acceptedSortDir }) } }, diff --git a/packages/web-app-files/src/views/shares/SharedWithOthers.vue b/packages/web-app-files/src/views/shares/SharedWithOthers.vue index 14ed1a249b0..b3ef6e77a60 100644 --- a/packages/web-app-files/src/views/shares/SharedWithOthers.vue +++ b/packages/web-app-files/src/views/shares/SharedWithOthers.vue @@ -27,6 +27,7 @@ :header-position="fileListHeaderY" :sort-by="sortBy" :sort-dir="sortDir" + :grouping-settings="groupingSettings" @file-click="triggerDefaultAction" @row-mounted="rowMounted" @sort="handleSort" @@ -63,7 +64,7 @@ import { mapGetters, mapState, mapActions } from 'vuex' import { useFileActions } from '../../composables/actions/files/useFileActions' import { VisibilityObserver } from 'web-pkg/src/observer' import { ImageDimension, ImageType } from 'web-pkg/src/constants' -import { debounce } from 'lodash-es' +import { debounce, find } from 'lodash-es' import ResourceTable from '../../components/FilesList/ResourceTable.vue' import AppLoadingSpinner from 'web-pkg/src/components/AppLoadingSpinner.vue' @@ -79,7 +80,8 @@ import { useResourcesViewDefaults } from '../../composables' import { defineComponent } from 'vue' import { Resource } from 'web-client' import { SpaceResource } from 'web-client/src/helpers' -import { useStore } from 'web-pkg/src/composables' +import { useGroupingSettings } from 'web-pkg/src/cern/composables' +import { useMutationSubscription, useStore } from 'web-pkg/src/composables' import { getSpaceFromResource } from 'web-app-files/src/helpers/resource/getSpace' const visibilityObserver = new VisibilityObserver() @@ -103,10 +105,34 @@ export default defineComponent({ return getSpaceFromResource({ spaces: store.getters['runtime/spaces/spaces'], resource }) } + const resourcesViewDefaults = useResourcesViewDefaults() + const { sortBy, sortDir, loadResourcesTask, selectedResourcesIds, paginatedResources } = + resourcesViewDefaults + + useMutationSubscription(['Files/UPDATE_RESOURCE_FIELD'], async (mutation) => { + if (mutation.payload.field === 'shareTypes') { + if (selectedResourcesIds.value.length !== 1) return + const id = selectedResourcesIds.value[0] + + const match = find(paginatedResources.value, { id }) + if (!match) return + + await loadResourcesTask.perform() + + const matchedNewResource = find(paginatedResources.value, { fileId: match.fileId }) + if (!matchedNewResource) return + + selectedResourcesIds.value = [matchedNewResource.id] + } + }) + return { ...useFileActions(), - ...useResourcesViewDefaults(), - getSpace + ...resourcesViewDefaults, + getSpace, + + // CERN + ...useGroupingSettings({ sortBy, sortDir }) } }, diff --git a/packages/web-app-files/src/views/spaces/DriveResolver.vue b/packages/web-app-files/src/views/spaces/DriveResolver.vue index 71dcdde9533..7aaccfb7a75 100644 --- a/packages/web-app-files/src/views/spaces/DriveResolver.vue +++ b/packages/web-app-files/src/views/spaces/DriveResolver.vue @@ -1,11 +1,14 @@ diff --git a/packages/web-app-text-editor/tests/unit/app.spec.ts b/packages/web-app-text-editor/tests/unit/app.spec.ts index 35903648582..337a0616c9e 100644 --- a/packages/web-app-text-editor/tests/unit/app.spec.ts +++ b/packages/web-app-text-editor/tests/unit/app.spec.ts @@ -16,19 +16,16 @@ import App from '../../src/App.vue' jest.mock('web-pkg/src/composables/appDefaults') describe('Text editor app', () => { - it('appTopBar always present', () => { - const { wrapper } = getWrapper() - expect(wrapper.find('app-top-bar-stub').exists()).toBeTruthy() - }) describe('different view states', () => { it('shows the loading spinner during loading', () => { const { wrapper } = getWrapper() expect(wrapper.find('oc-spinner-stub').exists()).toBeTruthy() }) - it('shows the editor after loading', async () => { + it('shows the editor and appTopBar after loading', async () => { const { wrapper } = getWrapper() await wrapper.vm.loadFileTask.last expect(wrapper.find('oc-spinner-stub').exists()).toBeFalsy() + expect(wrapper.find('app-top-bar-stub').exists()).toBeTruthy() expect(wrapper.find('oc-textarea-stub').exists()).toBeTruthy() }) }) diff --git a/packages/web-app-webfinger/l10n/translations.json b/packages/web-app-webfinger/l10n/translations.json index 7ad37bfbd54..ba5747bd040 100644 --- a/packages/web-app-webfinger/l10n/translations.json +++ b/packages/web-app-webfinger/l10n/translations.json @@ -1 +1 @@ -{"ar":{},"bg":{},"cs":{},"de":{"One moment please…":"Einen Moment bitte ...","Resolve destination":"Ziel auflösen","Something went wrong.":"Etwas ist schiefgelaufen.","Sorry!":"Entschuldigung!","We could not resolve the destination.":"Das Ziel konnte nicht aufgelöst werden.","Webfinger":"Webfinger","You are being redirected.":"Sie werden weitergeleitet."},"es":{"One moment please…":"Aguarde un momento…","Resolve destination":"Resolver destino","Something went wrong.":"Se produjo un error.","Sorry!":"Lo sentimos","We could not resolve the destination.":"No hemos podido resolver el destino.","Webfinger":"WebFinger","You are being redirected.":"Se le redirigirá."},"fr":{"One moment please…":"Veuillez patienter …","Sorry!":"Désolé !","You are being redirected.":"Vous allez être redirigé ..."},"gl":{},"he":{"One moment please…":"רק רגע בבקשה…","Resolve destination":"פתרון היעד","Something went wrong.":"משהו השתבש.","Sorry!":"מתנצלים!","We could not resolve the destination.":"לא הצלחנו לפתור את היעד.","Webfinger":"Webfinger","You are being redirected.":"מתבצעת הפניה."},"it":{"One moment please…":"Ancora un momento...","Resolve destination":"Risoluzione destinazione","Something went wrong.":"Qualcosa è andato storto.","Sorry!":"Ci scusiamo per il disagio!","We could not resolve the destination.":"Non è stato possibile risolvere la destinazione.","Webfinger":"Webfinger","You are being redirected.":"Reindirizzamento in corso."},"pl":{"One moment please…":"Proszę czekać…","Resolve destination":"Określanie miejsca docelowego","Something went wrong.":"Coś poszło nie tak.","Sorry!":"Przepraszam!","We could not resolve the destination.":"Nie mogliśmy określić miejsca docelowego.","Webfinger":"Webfinger","You are being redirected.":"Jesteś przekierowywany."},"ru":{},"sk":{},"sq":{"One moment please…":"Një sekondë, ju lutemi…","Resolve destination":"Ftillo vendmbërritjen","Something went wrong.":"Diç shkoi ters.","Sorry!":"Na ndjeni!","We could not resolve the destination.":"S’ftilluam dot vendmbërritjen.","You are being redirected.":"Po bëhet ridrejtimi juaj."},"tr":{"One moment please…":"Bir dakika lütfen...","Resolve destination":"Hedefi çöz","Something went wrong.":"Bir şeyler ters gitti.","Sorry!":"Üzgünüz!","We could not resolve the destination.":"Hedefi çözemedik.","Webfinger":"Ağ parmağı","You are being redirected.":"Yönlendiriliyorsunuz."}} \ No newline at end of file +{"ar":{},"bg":{"One moment please…":"Един момент, моля...","Resolve destination":"Разрешете дестинацията","Something went wrong.":"Нещо се обърка.","Sorry!":"Съжаляваме!","We could not resolve the destination.":"Дестинацията не беше открита.","Webfinger":"Webfinger","You are being redirected.":"Вие сте пренасочени."},"cs":{},"de":{"One moment please…":"Einen Moment bitte ...","Resolve destination":"Ziel auflösen","Something went wrong.":"Etwas ist schiefgelaufen.","Sorry!":"Entschuldigung!","We could not resolve the destination.":"Das Ziel konnte nicht aufgelöst werden.","Webfinger":"Webfinger","You are being redirected.":"Sie werden weitergeleitet."},"es":{"One moment please…":"Aguarde un momento…","Resolve destination":"Resolver destino","Something went wrong.":"Se produjo un error.","Sorry!":"Lo sentimos","We could not resolve the destination.":"No hemos podido resolver el destino.","Webfinger":"WebFinger","You are being redirected.":"Se le redirigirá."},"fr":{"One moment please…":"Veuillez patienter …","Sorry!":"Désolé !","You are being redirected.":"Vous allez être redirigé ..."},"gl":{},"he":{"One moment please…":"רק רגע בבקשה…","Resolve destination":"פתרון היעד","Something went wrong.":"משהו השתבש.","Sorry!":"מתנצלים!","We could not resolve the destination.":"לא הצלחנו לפתור את היעד.","Webfinger":"Webfinger","You are being redirected.":"מתבצעת הפניה."},"it":{"One moment please…":"Ancora un momento...","Resolve destination":"Risoluzione destinazione","Something went wrong.":"Qualcosa è andato storto.","Sorry!":"Ci scusiamo per il disagio!","We could not resolve the destination.":"Non è stato possibile risolvere la destinazione.","Webfinger":"Webfinger","You are being redirected.":"Reindirizzamento in corso."},"pl":{"One moment please…":"Proszę czekać…","Resolve destination":"Określanie miejsca docelowego","Something went wrong.":"Coś poszło nie tak.","Sorry!":"Przepraszam!","We could not resolve the destination.":"Nie mogliśmy określić miejsca docelowego.","Webfinger":"Webfinger","You are being redirected.":"Jesteś przekierowywany."},"ru":{},"sk":{},"sq":{"One moment please…":"Një sekondë, ju lutemi…","Resolve destination":"Ftillo vendmbërritjen","Something went wrong.":"Diç shkoi ters.","Sorry!":"Na ndjeni!","We could not resolve the destination.":"S’ftilluam dot vendmbërritjen.","You are being redirected.":"Po bëhet ridrejtimi juaj."},"tr":{"One moment please…":"Bir dakika lütfen...","Resolve destination":"Hedefi çöz","Something went wrong.":"Bir şeyler ters gitti.","Sorry!":"Üzgünüz!","We could not resolve the destination.":"Hedefi çözemedik.","Webfinger":"Ağ parmağı","You are being redirected.":"Yönlendiriliyorsunuz."}} \ No newline at end of file diff --git a/packages/web-client/l10n/translations.json b/packages/web-client/l10n/translations.json index d059a4c0ad0..added6468d7 100644 --- a/packages/web-client/l10n/translations.json +++ b/packages/web-client/l10n/translations.json @@ -1 +1 @@ -{"ar":{"Create":"أنشأ","custom permissions":"صلاحيات مخصصة","Custom permissions":"صلاحيات مخصصة","Delete":"حذف","Edit":"يحرر","Federated":"متحد","Group":"مجموعة","Guest":"ضيف","Internal":"داخلي","Link":"رابط","Share":"شارك","User":"مستخدم"},"bg":{"Create":"Създаване","custom permissions":"персонализирани разрешения","Custom permissions":"Персонализирани разрешения","Delete":"Изтриване","Deny":"Отказ","Deny access":"Отказан достъп","Edit":"Редактиране","Federated":"Федеративен","Group":"Група","Guest":"Гост","Internal":"Вътрешен","Link":"Връзка","no access":"без достъп","No access":"Без достъп","Share":"Споделяне","User":"Потребител"},"cs":{"Create":"Vytvořit","custom permissions":"vlastní oprávnění","Custom permissions":"Vlastní oprávnění","Delete":"Odstranit","Deny":"Zamítnout","Deny access":"Zamítnout přístup","Edit":"Upravit","Federated":"Federovaný","Group":"Skupina","Guest":"Host","Internal":"Interní","Link":"Odkaz","no access":"žádný přístup","No access":"Žádný přístup","Share":"Sdílet","User":"Uživatel"},"de":{"Anyone with the link can edit":"Alle mit dem Link können bearbeiten.","Anyone with the link can only upload, existing content is not revealed.":"Alle mit dem Link können nur hochladen; bestehende Inhalte werden nicht angezeigt.","Anyone with the link can upload":"Alle mit dem Link können hochladen.","Anyone with the link can view":"Alle mit dem Link können anzeigen.","Anyone with the link can view and download.":"Alle mit dem Link können anzeigen und herunterladen.","Anyone with the link can view, download and edit.":"Alle mit dem Link können anzeigen, herunterladen und bearbeiten.","Anyone with the link can view, download and upload.":"Alle mit dem Link können anzeigen, herunterladen und hochladen.","Anyone with the link can view, download, upload, edit, add and delete.":"Alle mit dem Link können anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen und löschen.","can edit":"kann bearbeiten","Can edit":"Kann bearbeiten","can manage":"kann verwalten","Can manage":"Kann verwalten","can upload":"kann hochladen","Can upload":"Kann hochladen","can view":"kann anzeigen","Can view":"Kann anzeigen","Create":"Erstellen","custom permissions":"benutzerdefinierte Berechtigungen","Custom permissions":"Benutzerdefinierte Berechtigungen","Delete":"Löschen","Deny":"Ablehnen","Deny access":"Verweigere Zugriff","Edit":"Bearbeiten","Federated":"Föderiert","Group":"Gruppe","Guest":"Gast","Internal":"Intern","invited people":"eingeladene Personen","invited People":"eingeladene Personen","Invited people":"Eingeladene Personen","Invited People":"Eingeladene Personen","Link":"Link","Link works only for invited people. Login is required.":"Link funktioniert nur für eingeladene Personen. Anmeldung ist erforderlich.","no access":"Verweigere Zugriff","No access":"Verweigere Zugriff","Only for invited people":"Nur für eingeladene Personen.","Secret File Drop":"File Drop (geheim)","Set custom permissions":"Benutzerdefinierte Berechtigungen vergeben","Share":"Teilen","User":"Person","View":"Anzeigen","View and download.":"Anzeigen und herunterladen","View, download and share.":"Anzeigen, herunterladen und teilen","View, download, and edit.":"Anzeigen, herunterladen und bearbeiten","View, download, edit and share file.":"Anzeigen, herunterladen, bearbeiten und teilen","View, download, upload, edit, add and delete.":"Anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen und löschen","View, download, upload, edit, add, delete and share.":"Anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen, löschen und teilen","View, download, upload, edit, add, delete, share and manage members.":"Anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen, löschen, teilen und Mitglieder verwalten"},"es":{"Anyone with the link can edit":"Cualquiera que tenga el enlace puede editarlo","Anyone with the link can only upload, existing content is not revealed.":"Cualquiera que tenga el enlace puede cargar solamente; no se revela el contenido existente.","Anyone with the link can upload":"Cualquiera que tenga el enlace puede cargar","Anyone with the link can view":"Cualquiera que tenga el enlace puede ver","Anyone with the link can view and download.":"Cualquiera que tenga el enlace puede ver y descargar","Anyone with the link can view, download and edit.":"Cualquiera que tenga el enlace puede ver, descargar y editar.","Anyone with the link can view, download and upload.":"Cualquiera que tenga el enlace puede ver, descargar y cargar.","Anyone with the link can view, download, upload, edit, add and delete.":"Cualquiera que tenga el enlace puede ver, descargar, cargar, editar, añadir y eliminar.","can edit":"puede editar","Can edit":"Puede editar","can manage":"puede gestionar","Can manage":"Puede gestionar","can upload":"puede cargar","Can upload":"Puede cargar","can view":"puede ver","Can view":"Puede ver","Create":"Crear","custom permissions":"permisos personalizados","Custom permissions":"Permisos personalizados","Delete":"Eliminar","Deny":" Denegar","Deny access":"Acceso denegado","Edit":"Editar","Federated":"Federado","Group":"Grupo","Guest":"Invitado","Internal":"Interno","invited people":"Personas invitadas","invited People":"Personas invitadas","Invited people":"Personas invitadas","Invited People":"Personas invitadas","Link":"Enlace","Link works only for invited people. Login is required.":"El enlace funciona solamente para personas invitadas. Se necesita acceder a la cuenta.","no access":"sin acceso","No access":"Sin acceso","Only for invited people":"Solo para personas invitadas","Set custom permissions":"Establecer permisos personalizados","Share":"Compartir","User":"Usuario","View":"Ver","View and download.":"Ver y descargar.","View, download and share.":"Ver, descargar y compartir.","View, download, and edit.":"Ver, descargar y editar.","View, download, edit and share file.":"Ver, descargar, editar y compartir el archivo.","View, download, upload, edit, add and delete.":"Ver, descargar, cargar, editar, añadir y eliminar.","View, download, upload, edit, add, delete and share.":"Ver, descargar, cargar, editar, añadir, eliminar y compartir.","View, download, upload, edit, add, delete, share and manage members.":"Ver, descargar, cargar, editar, añadir, eliminar, compartir y gestionar miembros."},"fr":{"Create":"Créer","custom permissions":"permissions personnalisées","Delete":"Supprimer","Edit":"Editer","Federated":"Fédéré","Group":"Groupe","Guest":" Invité","Link":"Lien","Share":"Partager","User":"Utilisateur"},"gl":{"Create":"Crear","Delete":"Eliminar","Edit":"Editar","Group":"Grupo","Guest":"Convidado","Share":"Compartir","User":"Usuario"},"he":{"Create":"יצירה","Delete":"מחיקה","Edit":"עריכה","Group":"קבוצה","Guest":"אורח","Internal":"פנימי","Link":"קישור","no access":"אין גישה","No access":"אין גישה","Share":"שיתוף","User":"משתמש"},"it":{"Anyone with the link can edit":"Chiunque abbia il collegamento può modificare","Anyone with the link can only upload, existing content is not revealed.":"Chiunque abbia il collegamento può solamente caricare, il contenuto esistente non sarà visibile.","Anyone with the link can upload":"Chiunque abbia il collegamento può caricare","Anyone with the link can view":"Chiunque abbia il collegamento può visualizzare","Anyone with the link can view and download.":"Chiunque abbia il collegamento può visualizzare e scaricare.","Anyone with the link can view, download and edit.":"Chiunque abbia il collegamento può visualizzare, scaricare e modificare.","Anyone with the link can view, download and upload.":"Chiunque abbia il collegamento può visualizzare, scaricare e caricare.","Anyone with the link can view, download, upload, edit, add and delete.":"Chiunque abbia il collegamento può visualizzare, scaricare, caricare, modificare, aggiungere e eliminare.","can edit":"può modificare","Can edit":"Può modificare","can manage":"può gestire","Can manage":"Può gestire","can upload":"può caricare","Can upload":"Può caricare","can view":"può visualizzare","Can view":"Può visualizzare","Create":"Crea","custom permissions":"permessi personalizzati","Custom permissions":"Permessi personalizzati","Delete":"Elimina","Deny":"Rifiuta","Deny access":"Negare l'accesso","Edit":"Modifica","Federated":"Federato","Group":"Gruppo","Guest":"Ospite","Internal":"Interno","invited people":"persone invitate","Link":"Collegamento","Link works only for invited people. Login is required.":"Collegamento funzionante solo per persone invitate. È necessario accedere.","no access":"nessun accesso","No access":"Nessun accesso","Only for invited people":"Solo per persone invitate","Secret File Drop":"Condivisione segreta \"File Drop\"","Set custom permissions":"Imposta le autorizzazioni personalizzate","Share":"Condividi","User":"Utente","View":"Visualizza","View and download.":"Visualizza e scarica.","View, download and share.":"Visualizza, scarica e condividi.","View, download, and edit.":"Visualizza, scarica e modifica.","View, download, edit and share file.":"Visualizza, scarica, modifica e condividi file.","View, download, upload, edit, add and delete.":"Visualizza, scarica, carica, modifica, aggiungi ed elimina.","View, download, upload, edit, add, delete and share.":"Visualizza, scarica, carica, modifica, aggiungi, elimina e condividi.","View, download, upload, edit, add, delete, share and manage members.":"Visualizza, scarica, carica, modifica, aggiungi, elimina, condividi e gestisci membri."},"pl":{"Anyone with the link can only upload, existing content is not revealed.":"Każdy kto ma link może jedynie przesłać, obecna zawartość nie zostanie pokazana.","Anyone with the link can upload":"Każdy kto ma link może przesłać.","Anyone with the link can view, download and upload.":"Każdy kto ma link może zobaczyć, pobrać i przesłać.","Anyone with the link can view, download, upload, edit, add and delete.":"Każdy kto ma link może zobaczyć, pobrać, przesłać, edytować, dodać i usunąć.","can upload":"może przesłać","Can upload":"Może przesłać","Create":"Utwórz","Delete":"Usuń","Edit":"Edycja","Group":"Grupa","Guest":"Gość","no access":"brak dostępu","No access":"Brak dostępu","Share":"Udostępnij","User":"Użytkownik","View, download and share.":"Zobaczyć, pobrać i udostępnić.","View, download, edit and share file.":"Zobaczyć, pobrać, edytować i udostępnić plik.","View, download, upload, edit, add and delete.":"Zobaczyć, pobrać, przesłać, edytować, dodać i usunąć.","View, download, upload, edit, add, delete and share.":"Zobaczyć, pobrać, przesłać, edytować, dodać, usunąć i udostępnić.","View, download, upload, edit, add, delete, share and manage members.":"Zobaczyć, pobrać, przesłać, edytować, dodać, usunąć, udostępnić i zarządzać uczestnikami."},"ru":{"Create":"создать","custom permissions":"пользовательские разрешения","Custom permissions":"Пользовательские разрешения","Delete":"удалить","Deny":"запретить","Deny access":"отказано в доступе","Edit":"Редактировать","Federated":"федеративный","Group":"Группа","Guest":"Гость","Internal":"Внутренний","Link":"ссылка","no access":"нет доступа","No access":"Нет доступа","Share":"Поделиться","User":"Пользователь"},"sk":{"Create":"Vytvoriť","Delete":"Odstrániť","Edit":"Upraviť"},"sq":{"Anyone with the link can edit":"Cilido që ka lidhjen mund të përpunojë","Anyone with the link can only upload, existing content is not revealed.":"Cilido që ka lidhjen mundet vetëm të ngarkojë, lënda ekzistuese nuk tregohet.","Anyone with the link can upload":"Cilido me lidhjen mund të ngarkojë","Anyone with the link can view":"Cilido me lidhjen mund të shohë","Anyone with the link can view and download.":"Cilido me lidhjen mund të shohë dhe shkarkojë.","Anyone with the link can view, download and edit.":"Cilido me lidhjen mund të shohë, shkarkojë dhe përpunojë.","Anyone with the link can view, download and upload.":"Cilido me lidhjen mund të shohë, shkarkojë dhe ngarkojë.","Anyone with the link can view, download, upload, edit, add and delete.":"Cilido me lidhjen mund të shohë, shkarkojë, ngarkojë, përpunojë, shtojë dhe fshijë.","can edit":"mund të përpunojë","Can edit":"Mund të përpunojë","can manage":"mund të administrojë","Can manage":"Mund të administrojë","can upload":"mund të ngarkojë","Can upload":"Mund të ngarkojë","can view":"mund të shohë","Can view":"Mund të shohë","Create":"Krijoje","custom permissions":"leje të përshtatura","Custom permissions":"Leje të përshtatura","Delete":"Fshije","Deny":"Mohoja","Deny access":"Mohoji hyrjen","Edit":"Përpunoni","Federated":"E federuar","Group":"Grup","Guest":"Mysafir","Internal":"I brendshëm","invited people":"persona të ftuar","invited People":"Persona të ftuar","Invited people":"Persona të ftuar","Invited People":"Persona të Ftuar","Link":"Lidhje","Link works only for invited people. Login is required.":"Lidhja funksionon vetëm për persona të ftuar. Lyp bërjen e hyrjes.","no access":"pa hyrje","No access":"Pa hyrje","Only for invited people":"Vetëm për persona të ftuar","Secret File Drop":"Hedhje e Fshehtë Kartele","Set custom permissions":"Ujdisni leje vetjake","Share":"Të ndajë me të tjerë","User":"Përdorues","View":"Të shohë","View and download.":"Të shohë dhe shkarkojë.","View, download and share.":"Të shohë, shkarkojë dhe ndajë me të tjerë.","View, download, and edit.":"Të shohë, shkarkojë dhe përpunojë.","View, download, edit and share file.":"Të shohë, shkarkojë, përpunojë dhe ndajë me të tjerët kartelën.","View, download, upload, edit, add and delete.":"Të shohë, shkarkojë, ngarkojë, përpunojë shtojë dhe fshijë.","View, download, upload, edit, add, delete and share.":"Të shohë, shkarkojë, ngarkojë, përpunojë shtojë, fshijë dhe ndajë me të tjerë.","View, download, upload, edit, add, delete, share and manage members.":"Të shohë, shkarkojë, ngarkojë, përpunojë shtojë, fshijë, ndajë me të tjerë dhe administrojë anëtarë."},"tr":{"Anyone with the link can edit":"Bağlantıya sahip olan herkes düzenleyebilir","Anyone with the link can only upload, existing content is not revealed.":"Bağlantıya sahip olan herkes yalnızca yükleme yapabilir, mevcut içerik açığa çıkmaz.","Anyone with the link can upload":"Bağlantıya sahip olan herkes yükleyebilir","Anyone with the link can view":"Bağlantıya sahip olan herkes görüntüleyebilir","Anyone with the link can view and download.":"Bağlantıya sahip olan herkes görüntüleyebilir ve indirebilir.","Anyone with the link can view, download and edit.":"Bağlantıya sahip olan herkes görüntüleyebilir, indirebilir ve düzenleyebilir.","Anyone with the link can view, download and upload.":"Bağlantıya sahip olan herkes görüntüleyebilir, indirebilir ve yükleyebilir.","Anyone with the link can view, download, upload, edit, add and delete.":"Bağlantıya sahip olan herkes görüntüleyebilir, indirebilir, yükleyebilir, düzenleyebilir, ekleyebilir ve silebilir.","can edit":"düzenleyebilir","Can edit":"Düzenleyebilir","can manage":"yönetebilir","Can manage":"Yönetebilir","can upload":"yükleyebilir","Can upload":"Yükleyebilir","can view":"görüntüleyebilir","Can view":"Görüntüleyebilir","Create":"Yarat","custom permissions":"özel izinler","Custom permissions":"Özel izinler","Delete":"Sil","Deny":"Reddet","Deny access":"Erişimi reddet","Edit":"Düzenle","Federated":"Birleştirilmiş","Group":"Grup","Guest":"Misafir","Internal":"Dahili","invited people":"davetli kişiler","invited People":"davetli Kişiler","Invited people":"Davetli kişiler","Invited People":"Davetli Kişiler","Link":"Bağlantı","Link works only for invited people. Login is required.":"Bağlantı yalnızca davet edilen kişiler için çalışır. Giriş gereklidir.","no access":"erişim yok","No access":"Erişim yok","Only for invited people":"Sadece davetli kişiler","Secret File Drop":"Gizli Dosya Bırakma","Set custom permissions":"Özel izinler ayarla","Share":"Paylaş","User":"Kullanıcı","View":"Görüntüle","View and download.":"Görüntüle ve indir.","View, download and share.":"Görüntüle, indir ve paylaş.","View, download, and edit.":"Görüntüle, indir ve düzenle.","View, download, edit and share file.":"Görüntüle, indir, düzenle ve dosya paylaş.","View, download, upload, edit, add and delete.":"Görüntüle, indir, yükle, düzenle, ekle ve sil.","View, download, upload, edit, add, delete and share.":"Görüntüle, indir, yükle, düzenle, ekle, sil ve paylaş.","View, download, upload, edit, add, delete, share and manage members.":"Görüntüle, indir, yükle, düzenle, ekle, sil, paylaş ve üyeleri yönet."}} \ No newline at end of file +{"ar":{"Create":"أنشأ","custom permissions":"صلاحيات مخصصة","Custom permissions":"صلاحيات مخصصة","Delete":"حذف","Edit":"يحرر","Federated":"متحد","Group":"مجموعة","Guest":"ضيف","Internal":"داخلي","Link":"رابط","Share":"شارك","User":"مستخدم"},"bg":{"Anyone with the link can edit":"Всеки, който има връзката може да редактира","Anyone with the link can only upload, existing content is not revealed.":"Всеки, който има връзката може само да качва, а съществуващото съдържание не се разкрива.","Anyone with the link can upload":"Всеки, който има връзката може да качва","Anyone with the link can view":"Всеки, който има връзка може да преглежда","Anyone with the link can view and download.":"Всеки, който разполага с връзката може да преглежда и изтегля.","Anyone with the link can view, download and edit.":"Всеки, който има връзката може да разглежда, изтегля и редактира.","Anyone with the link can view, download and upload.":"Всеки, който има връзката може да разглежда, изтегля и качва.","Anyone with the link can view, download, upload, edit, add and delete.":"Всеки, който има връзката може да разглежда, изтегля, качва, редактира, добавя и изтрива.","can edit":"може да редактира","Can edit":"Може да редактира","can manage":"може да управлява","Can manage":"Може да управлява","can upload":"може да качва","Can upload":"Може да качва","can view":"може да преглежда","Can view":"Може да преглежда","Create":"Създаване","custom permissions":"персонализирани разрешения","Custom permissions":"Персонализирани разрешения","Delete":"Изтриване","Deny":"Отказ","Deny access":"Отказан достъп","Edit":"Редактиране","Federated":"Федеративен","Group":"Група","Guest":"Гост","Internal":"Вътрешен","invited people":"поканени хора","invited People":"поканени Хора","Invited people":"поканени хора","Invited People":"поканени Хора","Link":"Връзка","Link works only for invited people. Login is required.":"Връзката функционира само за поканени хора. Изисква се вход.","no access":"без достъп","No access":"Без достъп","Only for invited people":"Само поканените хора","Secret File Drop":"Secret File Drop","Set custom permissions":"Задаване на персонализирани разрешения","Share":"Споделяне","User":"Потребител","View":"Преглед","View and download.":"Преглед и изтегляне.","View, download and share.":"Преглед, изтегляне и споделяне.","View, download, and edit.":"Преглед, изтегляне и редактиране.","View, download, edit and share file.":"Преглед, изтегляне, редактиране и споделяне на файла.","View, download, upload, edit, add and delete.":"Преглед, изтегляне, качване, редактиране и изтриване.","View, download, upload, edit, add, delete and share.":"Преглед, изтегляне, качване, редактиране, изтриване и споделяне.","View, download, upload, edit, add, delete, share and manage members.":"Преглед, изтегляне, качване, редактиране, изтриване, споделяне и управление на членове."},"cs":{"Create":"Vytvořit","custom permissions":"vlastní oprávnění","Custom permissions":"Vlastní oprávnění","Delete":"Odstranit","Deny":"Zamítnout","Deny access":"Zamítnout přístup","Edit":"Upravit","Federated":"Federovaný","Group":"Skupina","Guest":"Host","Internal":"Interní","Link":"Odkaz","no access":"žádný přístup","No access":"Žádný přístup","Share":"Sdílet","User":"Uživatel"},"de":{"Anyone with the link can edit":"Alle mit dem Link können bearbeiten.","Anyone with the link can only upload, existing content is not revealed.":"Alle mit dem Link können nur hochladen; bestehende Inhalte werden nicht angezeigt.","Anyone with the link can upload":"Alle mit dem Link können hochladen.","Anyone with the link can view":"Alle mit dem Link können anzeigen.","Anyone with the link can view and download.":"Alle mit dem Link können anzeigen und herunterladen.","Anyone with the link can view, download and edit.":"Alle mit dem Link können anzeigen, herunterladen und bearbeiten.","Anyone with the link can view, download and upload.":"Alle mit dem Link können anzeigen, herunterladen und hochladen.","Anyone with the link can view, download, upload, edit, add and delete.":"Alle mit dem Link können anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen und löschen.","can edit":"kann bearbeiten","Can edit":"Kann bearbeiten","can manage":"kann verwalten","Can manage":"Kann verwalten","can upload":"kann hochladen","Can upload":"Kann hochladen","can view":"kann anzeigen","Can view":"Kann anzeigen","Create":"Erstellen","custom permissions":"benutzerdefinierte Berechtigungen","Custom permissions":"Benutzerdefinierte Berechtigungen","Delete":"Löschen","Deny":"Ablehnen","Deny access":"Verweigere Zugriff","Edit":"Bearbeiten","Federated":"Föderiert","Group":"Gruppe","Guest":"Gast","Internal":"Intern","invited people":"eingeladene Personen","invited People":"eingeladene Personen","Invited people":"Eingeladene Personen","Invited People":"Eingeladene Personen","Link":"Link","Link works only for invited people. Login is required.":"Link funktioniert nur für eingeladene Personen. Anmeldung ist erforderlich.","no access":"Verweigere Zugriff","No access":"Verweigere Zugriff","Only for invited people":"Nur für eingeladene Personen.","Secret File Drop":"File Drop (geheim)","Set custom permissions":"Benutzerdefinierte Berechtigungen vergeben","Share":"Teilen","User":"Person","View":"Anzeigen","View and download.":"Anzeigen und herunterladen","View, download and share.":"Anzeigen, herunterladen und teilen","View, download, and edit.":"Anzeigen, herunterladen und bearbeiten","View, download, edit and share file.":"Anzeigen, herunterladen, bearbeiten und teilen","View, download, upload, edit, add and delete.":"Anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen und löschen","View, download, upload, edit, add, delete and share.":"Anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen, löschen und teilen","View, download, upload, edit, add, delete, share and manage members.":"Anzeigen, herunterladen, hochladen, bearbeiten, hinzufügen, löschen, teilen und Mitglieder verwalten"},"es":{"Anyone with the link can edit":"Cualquiera que tenga el enlace puede editarlo","Anyone with the link can only upload, existing content is not revealed.":"Cualquiera que tenga el enlace puede cargar solamente; no se revela el contenido existente.","Anyone with the link can upload":"Cualquiera que tenga el enlace puede cargar","Anyone with the link can view":"Cualquiera que tenga el enlace puede ver","Anyone with the link can view and download.":"Cualquiera que tenga el enlace puede ver y descargar","Anyone with the link can view, download and edit.":"Cualquiera que tenga el enlace puede ver, descargar y editar.","Anyone with the link can view, download and upload.":"Cualquiera que tenga el enlace puede ver, descargar y cargar.","Anyone with the link can view, download, upload, edit, add and delete.":"Cualquiera que tenga el enlace puede ver, descargar, cargar, editar, añadir y eliminar.","can edit":"puede editar","Can edit":"Puede editar","can manage":"puede gestionar","Can manage":"Puede gestionar","can upload":"puede cargar","Can upload":"Puede cargar","can view":"puede ver","Can view":"Puede ver","Create":"Crear","custom permissions":"permisos personalizados","Custom permissions":"Permisos personalizados","Delete":"Eliminar","Deny":" Denegar","Deny access":"Acceso denegado","Edit":"Editar","Federated":"Federado","Group":"Grupo","Guest":"Invitado","Internal":"Interno","invited people":"Personas invitadas","invited People":"Personas invitadas","Invited people":"Personas invitadas","Invited People":"Personas invitadas","Link":"Enlace","Link works only for invited people. Login is required.":"El enlace funciona solamente para personas invitadas. Se necesita acceder a la cuenta.","no access":"sin acceso","No access":"Sin acceso","Only for invited people":"Solo para personas invitadas","Set custom permissions":"Establecer permisos personalizados","Share":"Compartir","User":"Usuario","View":"Ver","View and download.":"Ver y descargar.","View, download and share.":"Ver, descargar y compartir.","View, download, and edit.":"Ver, descargar y editar.","View, download, edit and share file.":"Ver, descargar, editar y compartir el archivo.","View, download, upload, edit, add and delete.":"Ver, descargar, cargar, editar, añadir y eliminar.","View, download, upload, edit, add, delete and share.":"Ver, descargar, cargar, editar, añadir, eliminar y compartir.","View, download, upload, edit, add, delete, share and manage members.":"Ver, descargar, cargar, editar, añadir, eliminar, compartir y gestionar miembros."},"fr":{"Create":"Créer","custom permissions":"permissions personnalisées","Delete":"Supprimer","Edit":"Editer","Federated":"Fédéré","Group":"Groupe","Guest":" Invité","Link":"Lien","Share":"Partager","User":"Utilisateur"},"gl":{"Create":"Crear","Delete":"Eliminar","Edit":"Editar","Group":"Grupo","Guest":"Convidado","Share":"Compartir","User":"Usuario"},"he":{"Create":"יצירה","Delete":"מחיקה","Edit":"עריכה","Group":"קבוצה","Guest":"אורח","Internal":"פנימי","Link":"קישור","no access":"אין גישה","No access":"אין גישה","Share":"שיתוף","User":"משתמש"},"it":{"Anyone with the link can edit":"Chiunque abbia il collegamento può modificare","Anyone with the link can only upload, existing content is not revealed.":"Chiunque abbia il collegamento può solamente caricare, il contenuto esistente non sarà visibile.","Anyone with the link can upload":"Chiunque abbia il collegamento può caricare","Anyone with the link can view":"Chiunque abbia il collegamento può visualizzare","Anyone with the link can view and download.":"Chiunque abbia il collegamento può visualizzare e scaricare.","Anyone with the link can view, download and edit.":"Chiunque abbia il collegamento può visualizzare, scaricare e modificare.","Anyone with the link can view, download and upload.":"Chiunque abbia il collegamento può visualizzare, scaricare e caricare.","Anyone with the link can view, download, upload, edit, add and delete.":"Chiunque abbia il collegamento può visualizzare, scaricare, caricare, modificare, aggiungere e eliminare.","can edit":"può modificare","Can edit":"Può modificare","can manage":"può gestire","Can manage":"Può gestire","can upload":"può caricare","Can upload":"Può caricare","can view":"può visualizzare","Can view":"Può visualizzare","Create":"Crea","custom permissions":"permessi personalizzati","Custom permissions":"Permessi personalizzati","Delete":"Elimina","Deny":"Rifiuta","Deny access":"Negare l'accesso","Edit":"Modifica","Federated":"Federato","Group":"Gruppo","Guest":"Ospite","Internal":"Interno","invited people":"persone invitate","Link":"Collegamento","Link works only for invited people. Login is required.":"Collegamento funzionante solo per persone invitate. È necessario accedere.","no access":"nessun accesso","No access":"Nessun accesso","Only for invited people":"Solo per persone invitate","Secret File Drop":"Condivisione segreta \"File Drop\"","Set custom permissions":"Imposta le autorizzazioni personalizzate","Share":"Condividi","User":"Utente","View":"Visualizza","View and download.":"Visualizza e scarica.","View, download and share.":"Visualizza, scarica e condividi.","View, download, and edit.":"Visualizza, scarica e modifica.","View, download, edit and share file.":"Visualizza, scarica, modifica e condividi file.","View, download, upload, edit, add and delete.":"Visualizza, scarica, carica, modifica, aggiungi ed elimina.","View, download, upload, edit, add, delete and share.":"Visualizza, scarica, carica, modifica, aggiungi, elimina e condividi.","View, download, upload, edit, add, delete, share and manage members.":"Visualizza, scarica, carica, modifica, aggiungi, elimina, condividi e gestisci membri."},"pl":{"Anyone with the link can only upload, existing content is not revealed.":"Każdy kto ma link może jedynie przesłać, obecna zawartość nie zostanie pokazana.","Anyone with the link can upload":"Każdy kto ma link może przesłać.","Anyone with the link can view, download and upload.":"Każdy kto ma link może zobaczyć, pobrać i przesłać.","Anyone with the link can view, download, upload, edit, add and delete.":"Każdy kto ma link może zobaczyć, pobrać, przesłać, edytować, dodać i usunąć.","can upload":"może przesłać","Can upload":"Może przesłać","Create":"Utwórz","Delete":"Usuń","Edit":"Edycja","Group":"Grupa","Guest":"Gość","invited people":"zaproszone osoby","invited People":"zaproszone osoby","Invited people":"Zaproszone osoby","Invited People":"Zaproszone osoby","no access":"brak dostępu","No access":"Brak dostępu","Share":"Udostępnij","User":"Użytkownik","View":"Widok","View, download and share.":"Zobaczyć, pobrać i udostępnić.","View, download, edit and share file.":"Zobaczyć, pobrać, edytować i udostępnić plik.","View, download, upload, edit, add and delete.":"Zobaczyć, pobrać, przesłać, edytować, dodać i usunąć.","View, download, upload, edit, add, delete and share.":"Zobaczyć, pobrać, przesłać, edytować, dodać, usunąć i udostępnić.","View, download, upload, edit, add, delete, share and manage members.":"Zobaczyć, pobrać, przesłać, edytować, dodać, usunąć, udostępnić i zarządzać uczestnikami."},"ru":{"Create":"создать","custom permissions":"пользовательские разрешения","Custom permissions":"Пользовательские разрешения","Delete":"удалить","Deny":"запретить","Deny access":"отказано в доступе","Edit":"Редактировать","Federated":"федеративный","Group":"Группа","Guest":"Гость","Internal":"Внутренний","Link":"ссылка","no access":"нет доступа","No access":"Нет доступа","Share":"Поделиться","User":"Пользователь"},"sk":{"Create":"Vytvoriť","Delete":"Odstrániť","Edit":"Upraviť"},"sq":{"Anyone with the link can edit":"Cilido që ka lidhjen mund të përpunojë","Anyone with the link can only upload, existing content is not revealed.":"Cilido që ka lidhjen mundet vetëm të ngarkojë, lënda ekzistuese nuk tregohet.","Anyone with the link can upload":"Cilido me lidhjen mund të ngarkojë","Anyone with the link can view":"Cilido me lidhjen mund të shohë","Anyone with the link can view and download.":"Cilido me lidhjen mund të shohë dhe shkarkojë.","Anyone with the link can view, download and edit.":"Cilido me lidhjen mund të shohë, shkarkojë dhe përpunojë.","Anyone with the link can view, download and upload.":"Cilido me lidhjen mund të shohë, shkarkojë dhe ngarkojë.","Anyone with the link can view, download, upload, edit, add and delete.":"Cilido me lidhjen mund të shohë, shkarkojë, ngarkojë, përpunojë, shtojë dhe fshijë.","can edit":"mund të përpunojë","Can edit":"Mund të përpunojë","can manage":"mund të administrojë","Can manage":"Mund të administrojë","can upload":"mund të ngarkojë","Can upload":"Mund të ngarkojë","can view":"mund të shohë","Can view":"Mund të shohë","Create":"Krijoje","custom permissions":"leje të përshtatura","Custom permissions":"Leje të përshtatura","Delete":"Fshije","Deny":"Mohoja","Deny access":"Mohoji hyrjen","Edit":"Përpunoni","Federated":"E federuar","Group":"Grup","Guest":"Mysafir","Internal":"I brendshëm","invited people":"persona të ftuar","invited People":"Persona të ftuar","Invited people":"Persona të ftuar","Invited People":"Persona të Ftuar","Link":"Lidhje","Link works only for invited people. Login is required.":"Lidhja funksionon vetëm për persona të ftuar. Lyp bërjen e hyrjes.","no access":"pa hyrje","No access":"Pa hyrje","Only for invited people":"Vetëm për persona të ftuar","Secret File Drop":"Hedhje e Fshehtë Kartele","Set custom permissions":"Ujdisni leje vetjake","Share":"Të ndajë me të tjerë","User":"Përdorues","View":"Të shohë","View and download.":"Të shohë dhe shkarkojë.","View, download and share.":"Të shohë, shkarkojë dhe ndajë me të tjerë.","View, download, and edit.":"Të shohë, shkarkojë dhe përpunojë.","View, download, edit and share file.":"Të shohë, shkarkojë, përpunojë dhe ndajë me të tjerët kartelën.","View, download, upload, edit, add and delete.":"Të shohë, shkarkojë, ngarkojë, përpunojë shtojë dhe fshijë.","View, download, upload, edit, add, delete and share.":"Të shohë, shkarkojë, ngarkojë, përpunojë shtojë, fshijë dhe ndajë me të tjerë.","View, download, upload, edit, add, delete, share and manage members.":"Të shohë, shkarkojë, ngarkojë, përpunojë shtojë, fshijë, ndajë me të tjerë dhe administrojë anëtarë."},"tr":{"Anyone with the link can edit":"Bağlantıya sahip olan herkes düzenleyebilir","Anyone with the link can only upload, existing content is not revealed.":"Bağlantıya sahip olan herkes yalnızca yükleme yapabilir, mevcut içerik açığa çıkmaz.","Anyone with the link can upload":"Bağlantıya sahip olan herkes yükleyebilir","Anyone with the link can view":"Bağlantıya sahip olan herkes görüntüleyebilir","Anyone with the link can view and download.":"Bağlantıya sahip olan herkes görüntüleyebilir ve indirebilir.","Anyone with the link can view, download and edit.":"Bağlantıya sahip olan herkes görüntüleyebilir, indirebilir ve düzenleyebilir.","Anyone with the link can view, download and upload.":"Bağlantıya sahip olan herkes görüntüleyebilir, indirebilir ve yükleyebilir.","Anyone with the link can view, download, upload, edit, add and delete.":"Bağlantıya sahip olan herkes görüntüleyebilir, indirebilir, yükleyebilir, düzenleyebilir, ekleyebilir ve silebilir.","can edit":"düzenleyebilir","Can edit":"Düzenleyebilir","can manage":"yönetebilir","Can manage":"Yönetebilir","can upload":"yükleyebilir","Can upload":"Yükleyebilir","can view":"görüntüleyebilir","Can view":"Görüntüleyebilir","Create":"Yarat","custom permissions":"özel izinler","Custom permissions":"Özel izinler","Delete":"Sil","Deny":"Reddet","Deny access":"Erişimi reddet","Edit":"Düzenle","Federated":"Birleştirilmiş","Group":"Grup","Guest":"Misafir","Internal":"Dahili","invited people":"davetli kişiler","invited People":"davetli Kişiler","Invited people":"Davetli kişiler","Invited People":"Davetli Kişiler","Link":"Bağlantı","Link works only for invited people. Login is required.":"Bağlantı yalnızca davet edilen kişiler için çalışır. Giriş gereklidir.","no access":"erişim yok","No access":"Erişim yok","Only for invited people":"Sadece davetli kişiler","Secret File Drop":"Gizli Dosya Bırakma","Set custom permissions":"Özel izinler ayarla","Share":"Paylaş","User":"Kullanıcı","View":"Görüntüle","View and download.":"Görüntüle ve indir.","View, download and share.":"Görüntüle, indir ve paylaş.","View, download, and edit.":"Görüntüle, indir ve düzenle.","View, download, edit and share file.":"Görüntüle, indir, düzenle ve dosya paylaş.","View, download, upload, edit, add and delete.":"Görüntüle, indir, yükle, düzenle, ekle ve sil.","View, download, upload, edit, add, delete and share.":"Görüntüle, indir, yükle, düzenle, ekle, sil ve paylaş.","View, download, upload, edit, add, delete, share and manage members.":"Görüntüle, indir, yükle, düzenle, ekle, sil, paylaş ve üyeleri yönet."}} \ No newline at end of file diff --git a/packages/web-client/src/helpers/resource/types.ts b/packages/web-client/src/helpers/resource/types.ts index 5891add1490..42da0bec6a4 100644 --- a/packages/web-client/src/helpers/resource/types.ts +++ b/packages/web-client/src/helpers/resource/types.ts @@ -88,6 +88,8 @@ export interface Resource { canDisable?({ user }: { user?: User; ability?: any }): boolean canEditImage?({ user }: { user?: User }): boolean canEditReadme?({ user }: { user?: User }): boolean + canRemoveFromTrashbin?({ user }: { user?: User }): boolean + canEditSpaceQuota?(): boolean canEditTags?(): boolean diff --git a/packages/web-client/src/helpers/space/functions.ts b/packages/web-client/src/helpers/space/functions.ts index 404d15f3ed8..bc97195002b 100644 --- a/packages/web-client/src/helpers/space/functions.ts +++ b/packages/web-client/src/helpers/space/functions.ts @@ -17,6 +17,7 @@ import { urlJoin } from '../../utils' export function buildPublicSpaceResource(data): PublicSpaceResource { const publicLinkPassword = data.publicLinkPassword + const fileId = data.fileInfo?.[DavProperty.FileId] const publicLinkItemType = data.fileInfo?.[DavProperty.PublicLinkItemType] const publicLinkPermission = data.fileInfo?.[DavProperty.PublicLinkPermission] const publicLinkExpiration = data.fileInfo?.[DavProperty.PublicLinkExpiration] @@ -31,6 +32,7 @@ export function buildPublicSpaceResource(data): PublicSpaceResource { webDavPath: buildWebDavPublicPath(data.id) }), { + ...(fileId && { fileId }), ...(publicLinkPassword && { publicLinkPassword }), ...(publicLinkItemType && { publicLinkItemType }), ...(publicLinkPermission && { publicLinkPermission: parseInt(publicLinkPermission) }), @@ -194,6 +196,9 @@ export function buildSpace(data): SpaceResource { canEditReadme: function ({ user }: { user?: User } = {}) { return this.isManager(user) || this.isEditor(user) }, + canRemoveFromTrashbin: function ({ user }: { user?: User } = {}) { + return this.isManager(user) + }, canCreate: function () { return true }, diff --git a/packages/web-pkg/l10n/translations.json b/packages/web-pkg/l10n/translations.json index 5fd47bd896e..8174fec2727 100644 --- a/packages/web-pkg/l10n/translations.json +++ b/packages/web-pkg/l10n/translations.json @@ -1 +1 @@ -{"ar":{"Cancel":"إلغاء","Close":"غلق","Confirm":"تأكيد","Delete":"حذف","Details":"التفاصيل","Download failed":"فشل التحميل ","Edit quota":"تعديل الحصة النسبية","File could not be located":"تعذر تحديد موقع الملف","Manager":"مدير","Members":"اعضاء","No restriction":"لا قيود","Quota":"الحصه النسبيه","Rename":"إعادة تسمية","Save":"حفظ","Show context menu":"إظهار قائمة السياق"},"bg":{"(Opens in new window)":"(Отваря се в нов прозорец)","%{ itemCount } space selected":["%{ itemCount } избрано пространство","%{ itemCount } избрани пространства"],"%{displayName} (me)":"%{displayName} (аз)","%{linkShareCount} link giving access.":["%{linkShareCount} връзка, даваща достъп.","%{linkShareCount} връзки, даващи достъп."],"%{used} of %{total} used (%{percentage}% used)":"%{used} от %{total} използвани (%{percentage}% used)","%{used} used (no restriction)":"%{used} използвани (без ограничение)","Are you sure you want to delete the selected space?":["Сигурни ли сте, че искате да изтриете избраното пространство?","Сигурни ли сте, че искате да изтриете %{count} избрани пространства?"],"Back to %{panel} panel":"Назад към панела %{panel}","Cancel":"Отказ","Change quota":"Промяна на квота","Change quota for %{count} Spaces":"Промяна на квотата за %{count} Пространства","Change quota for %{count} users":"Промяна на квотата за %{count} потребителя","Change quota for Space \"%{name}\"":"Промяна на квотата за Пространството \"%{name}\"","Change quota for user \"%{name}\"":"Промяна на квотата за потребителя \"%{name}\"","Change subtitle for space":"Промяна на подзаглавието за пространството","Changes saved":"Промените са запазени","Close":"Затваряне","Close file sidebar":"Затваряне на страничната лента на файла","Confirm":"Потвърждаване","Delete":"Изтриване","Delete (%{count})":"Изтриване на (%{count})","Details":"Подробности","Disable":"Деактивиране","Disable (%{count})":"Деактивиране на (%{count})","Disabled:":"Деактивирано:","Display customization options of the files list":"Показване на опциите за персонализиране на списъка с файлове","Download failed":"Неуспешно изтегляне","Edit description":"Редактиране на описание","Edit description for space %{name}":"Редактиране на описанието на пространството %{name}","Edit quota":"Редактиране на квота","Edit subtitle":"Редактиране на подзаглавие","Enable":"Активиране","Enable (%{count})":"Активиране на (%{count})","Enabled:":"Активирано:","Failed to change quota":"Неуспех при промяна на квотата","Failed to change space quota":["Неуспешна промяна на квотата за пространството","Неуспешна промяна на квотата за %{count} пространства"],"Failed to change space subtitle":"Неуспешна промяна на подзаглавието на пространството","Failed to change user quota":["Неуспешна промяна на квотата за потребителя","Неуспешна промяна на квотата за %{count} потребителя"],"Failed to delete space %{spaceName}":"Неуспешно изтриване на пространството %{spaceName}","Failed to disable space %{spaceName}":"Неуспешно деактивиране на пространството %{spaceName}","Failed to edit space description":"Неуспешно редактиране на описанието на пространството","Failed to rename space":"Неуспех при преименуването на пространство","Failed to restore space %{spaceName}":"Неуспешно възстановяване на пространството %{spaceName}","Failed to set space description":"Неуспешно задаване на описание на пространството","File could not be located":"Файлът не може да бъде намерен","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Ако деактивирате избраното пространство, достъпът до него вече няма да е възможен. Само мениджърите на пространството ще имат достъп до него. Забележка: Никакви файлове няма да бъдат изтрити от сървъра.","Ако деактивирате избраните %{count} пространства, те вече няма да могат да бъдат достъпни. Само мениджърите на пространства все още ще имат достъп. Забележка: Никакви файлове няма да бъдат изтрити от сървъра."],"If you enable the selected space, it can be accessed again.":["Ако активирате избраното пространство, то ще бъде отново достъпно.","Ако активирате избраните пространства %{count}, те ще бъдат отново достъпни."],"Items per page":"Елементи на страница","Last activity":"Последна активност","Manager":"Мениджър","Members":"Членове","No changes":"Без промени","No restriction":"Без ограничение","Open link list in share panel":"Отваряне на списъка с връзки в панела за споделяне","Open member list in share panel":"Отваряне на списъка с членове в панела за споделяне","Open share panel":"Отваряне на панела за споделяне","Overview of the information about the selected space":"Преглед на информацията за избраното пространство","Overview of the information about the selected spaces":"Преглед на информацията за избраните пространства","Please enter a value equal to or less than %{ quotaLimit }":"Моля въведете стойност, равна или по-малка от %{ quotaLimit }","Please enter only numbers":"Моля, въведете само числа","Quota":"Квота","Quota was changed successfully":"Квотата е променена успешно","Remaining quota:":"Оставаща квота:","Rename":"Преименуване","Rename space":"Преименуване на пространството","Revert":"Връщане","Save":"Запазване","Select a space to view details":"Изберете пространство, за да видите подробности","Set as space description":"Задаване като описание на пространството","Show":"Показване","Show context menu":"Показване на контекстното меню","Show file extensions":"Показване на файловите разширения","Show hidden files":"Показване на скрити файлове","Space description":"Описание на пространството","Space description was edited successfully":"Описанието на пространството е успешно редактирано","Space description was set successfully":"Описанието на пространството е успешно зададено","Space name":"Име на пространството","Space name cannot be empty":"Името на пространството не може да бъде празно","Space name cannot exceed 255 characters":"Името на пространството не може да надвишава 255 символа","Space name was changed successfully":"Името на пространството е успешно променено","Space quota was changed successfully":["Квотата на пространството е променена успешно","Квотата на %{count} пространства е променена успешно"],"Space subtitle":"Подзаглавие на пространство","Space subtitle was changed successfully":"Подзаглавието на пространството е успешно променено","Space was deleted successfully":["Пространството беше изтрито успешно","Пространствата бяха изтрити успешно"],"Space was disabled successfully":["Пространството беше деактивирано успешно","Пространствата бяха деактивирани успешно"],"Subtitle":"Подзаглавие","Switch to condensed table view":"Превключване към съкратен изглед на таблицата","Switch to default table view":"Превключване към изглед на таблицата по подразбиране","Switch to tiles view":"Превключване към изглед с плочки","This space has %{memberShareCount} member.":["Това пространство има %{memberShareCount} член.","Това пространство има %{memberShareCount} члена."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Това пространство има %{memberShareCount} члена и %{linkShareCount} връзки.","This space has %{memberShareCount} members and one link.":"Това пространство има %{memberShareCount} члена и една връзка.","This space has one member and %{linkShareCount} link.":["Това пространство има един член и %{linkShareCount} връзка.","Това пространство има един член и %{linkShareCount} връзки."],"Tile size":"Размер на плочките","Toggle selection":"Превключване на избора","Total quota:":"Обща квота:","Unsaved changes":"Промените нe са запазени","Used quota:":"Използвана квота:","User quota was changed successfully":["Квотата на потребителя е променена успешно","Квотата на %{count} потребителя е променена успешно"]},"cs":{"(Opens in new window)":"(Otevře se v novém okně)","%{ itemCount } space selected":["%{ itemCount } místo vybráno","%{ itemCount } místa vybrána","%{ itemCount } míst vybráno","%{ itemCount } míst vybráno"],"%{displayName} (me)":"%{displayName} (já)","%{linkShareCount} link giving access.":["%{linkShareCount} odkaz zprostředkovávající přistup.","%{linkShareCount} odkazy zprostředkovávající přistup.","%{linkShareCount} odkazů zprostředkovávajících přistup.","%{linkShareCount} odkazů zprostředkovávajících přistup."],"%{used} of %{total} used (%{percentage}% used)":"%{used} z %{total} využito (%{percentage}% využito)","%{used} used (no restriction)":"%{used} využito (bez omezení)","Are you sure you want to delete the selected space?":["Opravdu smazat vybrané místo?","Opravdu smazat %{count} vybraná místa?","Opravdu smazat %{count} vybraných míst?","Opravdu smazat %{count} vybraných míst?"],"Back to %{panel} panel":"Zpátky k %{panel} panelu","Cancel":"Zrušit","Change quota":"Změnit kvótu","Change quota for %{count} Spaces":"Změnit kvótu pro %{count} míst","Change quota for %{count} users":"Změnit kvótu pro %{count} uživatelů ","Change quota for Space \"%{name}\"":"Změnit kvótu pro místo \"%{name}\"","Change quota for user \"%{name}\"":"Změnit kvótu pro uživatele \"%{name}\"","Change subtitle for space":"Změnit podtitulek pro místo","Changes saved":"Změny uloženy","Close":"Zavřít","Close file sidebar":"Zavřít postranní panel souboru","Confirm":"Potvrdit","Delete":"Odstranit","Delete (%{count})":"Smazat (%{count})","Delete Space \"%{space}\"?":["Smazat %{spaceCount} místo?","Smazat %{spaceCount} místa?","Smazat %{spaceCount} míst?","Smazat %{spaceCount} míst?"],"Details":"Detaily","Disable":"Deaktivovat","Disable (%{count})":"Deaktivovat (%{count})","Disable Space \"%{space}\"?":["Deaktivovat místo \"%{space}\"?","Deaktivovat %{spaceCount} místa?","Deaktivovat %{spaceCount} míst?","Deaktivovat %{spaceCount} míst?"],"Disabled:":"Deaktivováno:","Download failed":"Stahování selhalo","Edit description":"Změnit popisek","Edit description for space %{name}":"Změnit popisek místa %{name}","Edit quota":"Změnit kvótu","Edit subtitle":"Změnit titulek","Enable":"Povolit","Enable (%{count})":"Aktivovat (%{count})","Enable Space \"%{space}\"?":["Aktivovat místo \"%{space}\"?","Aktivovat %{spaceCount} místa?","Aktivovat %{spaceCount} míst?","Aktivovat %{spaceCount} míst?"],"Enabled:":"Povoleno:","Failed to change quota":"Nezdařilo se změnit kvótu","Failed to change space quota":["Nezdařilo se změnit kvótu místa","Nezdařilo se změnit kvótu pro %{count} místa","Nezdařilo se změnit kvótu pro %{count} míst","Nezdařilo se změnit kvótu pro %{count} míst"],"Failed to change space subtitle":"Nezdařilo se změnit podtitulek místa","Failed to change user quota":["Nezdařilo se změnit kvótu uživatele","Nezdařilo se změnit kvótu pro %{count} uživatele","Nezdařilo se změnit kvótu pro %{count} uživatelů","Nezdařilo se změnit kvótu pro %{count} uživatelů"],"Failed to delete space %{spaceName}":"Nezdařilo se smazat místo %{spaceName}","Failed to disable space %{spaceName}":"Nezdařilo se deaktivovat místo %{spaceName}","Failed to edit space description":"Nezdařilo se editovat popisek místa","Failed to rename space":"Nezdařilo se přejmenovat místo","Failed to restore space %{spaceName}":"Nezdařilo se obnovit místo %{spaceName}","Failed to set space description":"Nezdařilo se nastavit popisek místa","File could not be located":"Soubor se nepodařilo najít","Filter list":"Seznam filtrů","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Pokud vybrané místo bude deaktivováno, nebude nadále přístupné. Pouze Manažeři místa budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru.","Pokud %{count} vybraná místa budou deaktivována, nebudou nadále přístupná. Pouze Manažeři míst budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru.","Pokud %{count} vybraných míst bude deaktivováno, nebudou nadále přístupná. Pouze Manažeři míst budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru.","Pokud %{count} vybraných míst bude deaktivováno, nebudou nadále přístupná. Pouze Manažeři míst budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru."],"If you enable the selected space, it can be accessed again.":["Pokud bude vybrané místo aktivováno, bude možno k němu opět přistupovat.","Pokud budou %{count} vybraná místa aktivována, bude možno k nim opět přistupovat.","Pokud bude %{count} vybraných míst aktivováno, bude možno k nim opět přistupovat.","Pokud bude %{count} vybraných míst aktivováno, bude možno k nim opět přistupovat."],"Last activity":"Poslední aktivita","Manager":"Manažer","Members":"Členové","No changes":"Žádné změny","No restriction":"Žádná omezení","Open link list in share panel":"Otevřít seznam odkazů v panelu sdílení ","Open member list in share panel":"Otevřít seznam členů v panelu sdílení","Open share panel":"Otevřít panel sdílení","Overview of the information about the selected space":"Přehled informací o vybraném místě","Overview of the information about the selected spaces":"Přehled informací o vybraných místech","Please enter a value equal to or less than %{ quotaLimit }":"Prosím vložte hodnotu menší nebo rovnou %{ quotaLimit }","Please enter only numbers":"Prosím vkládejte pouze čísla","Quota":"Kvóta","Quota was changed successfully":"Kvóta byla změněna úspěšně","Remaining quota:":"Zbývající kvóta:","Rename":"Přejmenovat","Rename space":"Přejmenovat místo","Revert":"Vrátit zpět","Save":"Uložit","Select a space to view details":"Vybrat místo pro prohlížení detailů","Set as space description":"Nastavit jako popisek místa","Show":"Ukázat","Show context menu":"Ukázat kontextovou nabídku","Space description":"Popisek místa","Space description was edited successfully":"Popisek místa byl úspěšně editován","Space description was set successfully":"Popisek místa byl úspěšně nastaven","Space name":"Název místa","Space name cannot be empty":"Název místa nesmí být prázdný","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Název místa nesmí obsahovat následující znaku: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Název místa nesmí překročit 255 znaků","Space name was changed successfully":"Název místa byl úspěšně změněn","Space quota was changed successfully":["Kvóta místa byla změněna úspěšně","Kvóta %{count} míst byla změněna úspěšně","Kvóta %{count} míst byla změněna úspěšně","Kvóta %{count} míst byla změněna úspěšně"],"Space subtitle":"Podtitulek místa","Space subtitle was changed successfully":"Podtitulek místa byl změněn úspěšně","Space was deleted successfully":["Místo bylo smazáno úspěšně","Místa byla smazána úspěšně","Místa byla smazána úspěšně","Místa byla smazána úspěšně"],"Space was disabled successfully":["Místo bylo deaktivováno úspěšně","Místa byla deaktivována úspěšně","Místa byla deaktivována úspěšně","Místa byla deaktivována úspěšně"],"Space was enabled successfully":["Místo bylo aktivováno úspěšně","Místa byla aktivována úspěšně","Místa byla aktivována úspěšně","Místa byla aktivována úspěšně"],"Subtitle":"Podtitulek","This space has %{memberShareCount} member.":["Toto místo má %{memberShareCount} člena.","Toto místo má %{memberShareCount} členy.","Toto místo má %{memberShareCount} členů.","Toto místo má %{memberShareCount} členů."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Toto místo má %{memberShareCount} členů a %{linkShareCount} odkazů.","This space has %{memberShareCount} members and one link.":"Toto místo má %{memberShareCount} členů a jeden odkaz.","This space has one member and %{linkShareCount} link.":["Toto místo má jednoho člena a %{linkShareCount} odkaz.","Toto místo má jednoho člena a %{linkShareCount} odkazy.","Toto místo má jednoho člena a %{linkShareCount} odkazů.","Toto místo má jednoho člena a %{linkShareCount} odkazů."],"Toggle selection":"Přepínací výběr","Total quota:":"Celková kvóta:","Unsaved changes":"Neuložené změny","Used quota:":"Využitá kvóta:","User quota was changed successfully":["Kvóta uživatele byla změněna úspěšně","Kvóta %{count} uživatelů byla změněna úspěšně","Kvóta %{count} uživatelů byla změněna úspěšně","Kvóta %{count} uživatelů byla změněna úspěšně"]},"de":{"(Opens in new window)":"(Wird in neuem Fenster geöffnet)","%{ itemCount } space selected":["%{ itemCount } spaces ausgewählt","%{ itemCount } Spaces ausgewählt"],"%{displayName} (me)":"%{displayName} (ich)","%{linkShareCount} link giving access.":["%{linkShareCount} Link gewährt Zugriff.","%{linkShareCount} Links gewähren Zugriff."],"%{used} of %{total} used (%{percentage}% used)":"%{used} von %{total} benutzt (%{percentage}% verwendet)","%{used} used (no restriction)":"%{used} benutzt (unbegrenzt)","Are you sure you want to delete the selected space?":["Soll der ausgewählte Space wirklich gelöscht werden?","Sollen %{count} ausgewählte Spaces wirklich gelöscht werden?"],"Back to %{panel} panel":"Zurück zum %{panel} Panel","Cancel":"Abbrechen","Change quota":"Quota ändern","Change quota for %{count} Spaces":"Quota für %{count} Spaces ändern","Change quota for %{count} users":"Quota für %{count} Personen ändern","Change quota for Space \"%{name}\"":"Quota für den Space %{name} ändern","Change quota for user \"%{name}\"":"Ändere Quota für Person \"%{name}\"","Change subtitle for space":"Untertitel für diesen Space ändern","Changes saved":"Änderungen gespeichert","Close":"Schließen","Close file sidebar":"Seitenleiste schließen","Confirm":"Bestätigen","Delete":"Löschen","Delete (%{count})":"Lösche (%{count})","Delete Space \"%{space}\"?":["Space \"%{space}\" löschen?","%{spaceCount} Spaces löschen?"],"Details":"Details","Disable":"Deaktivieren","Disable (%{count})":"Deaktiviere (%{count})","Disable Space \"%{space}\"?":["Space \"%{space}\" deaktivieren?","%{spaceCount} Spaces deaktivieren?"],"Disabled:":"Deaktiviert","Display customization options of the files list":"Anpassungsoptionen für die Dateienliste anzeigen","Download failed":"Fehler beim Herunterladen","Edit description":"Beschreibung bearbeiten","Edit description for space %{name}":"Beschreibung für Space %{name} bearbeiten","Edit quota":"Quota ändern","Edit subtitle":"Untertitel bearbeiten","Enable":"Aktivieren","Enable (%{count})":"(%{count}) aktivieren","Enable Space \"%{space}\"?":["Space \"%{space}\" aktivieren?","%{spaceCount} Spaces aktivieren?"],"Enabled:":"Aktiviert","Failed to change quota":"Quota-Änderung fehlgeschlagen","Failed to change space quota":["Fehler beim Ändern der Space-Quota","Fehler beim Ändern der Quota für %{count} Spaces"],"Failed to change space subtitle":"Fehler beim Ändern des Space-Untertitels","Failed to change user quota":["Fehler beim Ändern der Benutzerquota","Fehler beim Ändern der Quota für %{count} Personen"],"Failed to delete space %{spaceName}":"Fehler beim Löschen des Space %{spaceName}","Failed to disable space %{spaceName}":"Fehler beim Deaktivieren des Space %{spaceName}","Failed to edit space description":"Fehler beim Ändern der Space-Beschreibung","Failed to rename space":"Fehler beim Umbenennen des Space","Failed to restore space %{spaceName}":"Fehler beim Wiederherstellen des Space %{spaceName}","Failed to set space description":"Fehler beim Speichern der Space-Beschreibung","File could not be located":"Datei konnte nicht gefunden werden","Filter list":"Liste filtern","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Wenn Sie den ausgewählten Space deaktivieren, kann auf ihn nicht mehr zugegriffen werden. Nur Space-Manager haben noch Zugriff.\nHinweis: Es werden keine Dateien von dem Server gelöscht.","Wenn Sie die %{count} ausgewählten Spaces deaktivieren, kann auf sie nicht mehr zugegriffen werden. Nur Space-Verwalter/-innen haben noch Zugriff.\nHinweis: Es werden keine Dateien von dem Server gelöscht."],"If you enable the selected space, it can be accessed again.":["Wenn Sie den ausgewählten Space aktivieren, kann auf ihn wieder zugegriffen werden.","Wenn Sie die %{count} ausgewählten Spaces aktivieren, kann auf sie wieder zugegriffen werden."],"Items per page":"Dateien pro Seite","Last activity":"Letzte Aktivitäten","Manager":"Manager/-in","Members":"Mitglieder","No changes":"Keine Änderungen","No restriction":"Unbegrenzt","Open link list in share panel":"Liste der Linkfreigaben in der Seitenleiste öffnen","Open member list in share panel":"Liste der Mitglieder in der Seitenleiste öffnen","Open share panel":"Geteilt-mit Bereich öffnen","Overview of the information about the selected space":"Alle Infos zum ausgewählten Space","Overview of the information about the selected spaces":"Alle Infos zum ausgewählten Space","Please enter a value equal to or less than %{ quotaLimit }":"Bitte geben Sie einen Wert ein, der gleich oder kleiner als %{ quotaLimit } ist.","Please enter only numbers":"Bitte nur Zahlen eingeben.","Quota":"Quota","Quota was changed successfully":"Quota wurde erfolgreich geändert.","Remaining quota:":"Verbleibende Quota","Rename":"Umbenennen","Rename space":"Space umbenennen","Revert":"Zurücknehmen","Save":"Speichern","Select a space to view details":"Wählen Sie einen Space aus, um hier Details anzuzeigen.","Set as space description":"Als Space-Beschreibung festlegen","Show":"Anzeigen","Show context menu":"Kontextmenü anzeigen","Show file extensions":"Dateiendungen anzeigen","Show hidden files":"Versteckte Dateien anzeigen","Space description":"Space-Beschreibung","Space description was edited successfully":"Die Space-Beschreibung wurde erfolgreich bearbeitet.","Space description was set successfully":"Die Space-Beschreibung wurde erfolgreich festgelegt.","Space name":"Name des Spaces","Space name cannot be empty":"Der Name des Spaces darf nicht leer sein.","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Space-Namen dürfen folgende Zeichen nicht enthalten: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Der Space-Name darf nicht länger als 255 Zeichen sein.","Space name was changed successfully":"Der Space-Name wurde erfolgreich geändert.","Space quota was changed successfully":["Die Space-Quota wurde erfolgreich geändert.","Quota für %{count} Spaces wurde erfolgreich geändert."],"Space subtitle":"Space-Untertitel","Space subtitle was changed successfully":"Der Space-Untertitel wurde erfolgreich geändert.","Space was deleted successfully":["Der Space wurde erfolgreich gelöscht.","Die Spaces wurden erfolgreich gelöscht."],"Space was disabled successfully":["Der Space wurde erfolgreich deaktiviert.","Die Spaces wurden erfolgreich deaktiviert."],"Space was enabled successfully":["Der Space wurde erfolgreich aktiviert.","Die markierten Spaces wurden erfolgreich aktiviert."],"Subtitle":"Untertitel","Switch to condensed table view":"Zur verdichteten Tabellenansicht wechseln","Switch to default table view":"Zur normalen Tabellenansicht wechseln","Switch to tiles view":"Zur Kachelansicht wechseln","This space has %{memberShareCount} member.":["Dieser Space hat %{memberShareCount} Mitglied.","Dieser Space hat %{memberShareCount} Mitglieder."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Dieser Space hat %{memberShareCount} Mitglieder und %{linkShareCount} Links.","This space has %{memberShareCount} members and one link.":"Dieser Space hat %{memberShareCount} Mitglieder und einen Link.","This space has one member and %{linkShareCount} link.":["Dieser Space hat ein Mitglied und %{linkShareCount} Link.","Dieser Space hat ein Mitglied und %{linkShareCount} Links."],"Tile size":"Kachelgröße","Toggle selection":"Auswahl umschalten","Total quota:":"Gesamtquota","Unsaved changes":"Ungespeicherte Änderungen","Used quota:":"Benutzte Quota","User quota was changed successfully":["Benutzerquota erfolgreich geändert","Quota für %{count} Personen wurde erfolgreich geändert."]},"es":{"(Opens in new window)":"(Abre en nueva ventana)","Cancel":"Cancelar","Change quota":"Cambiar cuota","Change quota for %{count} Spaces":"Cambiar la cuota para el espacio %{count}","Close":"Cerrar","Confirm":"Confirmar","Delete":"Eliminar","Delete (%{count})":"Eliminada (%{count})","Details":"Detalles","Disable (%{count})":"Deshabilitada (%{count})","Disabled:":"Deshabilitado","Download failed":"Descarga fallida","Edit quota":"Editar cuota","Enable (%{count})":"Habilitada (%{count})","Enabled:":"Habilitado","Failed to change quota":"Fallo al cambiar la cuota","Failed to change space subtitle":"Error al cambiar el subtítulo del espacio","Failed to edit space description":"Error al editar la descripción del espacio","Failed to rename space":"Error al renombrar el espacio","File could not be located":"No se ha podido encontrar el archivo","Filter list":"Filtrar lista","Items per page":"Elementos por página","Manager":"Gestor","Members":"Miembros","No restriction":"Sin restricciones","Quota":"Cuota","Remaining quota:":"Cuota restante","Rename":"Renombrar","Save":"Guardar","Show context menu":"Mostrar menú contextual","Total quota:":"Cuota total","Unsaved changes":"Cambios sin guardar","Used quota:":"Cuota utilizada"},"fr":{"(Opens in new window)":"(ouvre dans une nouvelle fenêtre)","%{displayName} (me)":"%{displayName} (Moi)","%{linkShareCount} link giving access.":["%{linkShareCount} lien donnant accès.","%{linkShareCount} liens donnant accès","%{linkShareCount} liens donnant accès"],"%{used} of %{total} used (%{percentage}% used)":"%{used} utilisé sur %{total} (%{percentage}% utilisée)","%{used} used (no restriction)":"%{used} utilisée (Pas de restriction)","Cancel":"Annuler","Change subtitle for space":"Changer les sous-titres de l''espace","Close":"Fermer","Close file sidebar":"Ouvrir le panneau latéral de fichier.","Confirm":"Confirmer","Delete":"Supprimer","Details":"Détails","Disable":"Désactiver","Display customization options of the files list":"Afficher les options de customisation de la liste de fichier","Download failed":"Echec de téléchargement","Edit description":"Modifier la description","Edit quota":"Modifier le quota","Edit subtitle":"Modifier les sous-titres","Enable":"Activer","File could not be located":"Le fichier n'a pas été trouvé","Items per page":"Éléments par page","Last activity":"Dernière activité","Manager":"Gestionnaire","Members":"Membres","No changes":"Aucune modification","No restriction":"Aucune restriction","Please enter only numbers":"Merci de ne saisir que des chiffres","Quota":"Quota","Rename":"Renommer","Revert":"Rétablir","Save":"Enregistrer","Show file extensions":"Afficher les extensions du fichier","Show hidden files":"Afficher les fichiers masqués","Subtitle":"Sous-titre","Unsaved changes":"Modifications non enregistrées"},"gl":{"Cancel":"Cancelar","Confirm":"Confirmar","Delete":"Eliminar","Download failed":"Produciuse un fallo na descarga","File could not be located":"Non foi posíbel localizar o ficheiro","Rename":"Renomear","Save":"Gardar"},"he":{"(Opens in new window)":"(נפתח בחלון חדש)","%{displayName} (me)":"%{displayName} (אני)","%{linkShareCount} link giving access.":["קישור אחד מעניק גישה.","%{linkShareCount} קישורים מעניקים גישה.","%{linkShareCount} קישורים מעניקים גישה.","%{linkShareCount} קישורים מעניקים גישה."],"%{used} of %{total} used (%{percentage}% used)":"%{used} מתוך %{total} מנוצלים (%{percentage}% מנוצלים)","%{used} used (no restriction)":"%{used} מנוצלים (אין הגבלה)","Cancel":"ביטול","Change quota":"שינוי מכסה","Change subtitle for space":"החלפת כתובית למרחב","Changes saved":"שינויים נשמרו","Close":"סגירה","Close file sidebar":"סגירת סרגל הצד של הקבצים","Confirm":"אישור","Delete":"מחיקה","Delete (%{count})":"מחיקה (%{count})","Details":"פרטים","Disable":"השבתה","Disable (%{count})":"השבתה (%{count})","Disabled:":"מושבתים:","Display customization options of the files list":"הצגת אפשרות כוונון לרשימת הקבצים","Download failed":"הורדה נכשלה","Edit description":"עריכת תיאור","Edit description for space %{name}":"עריכת תיאור המרחב %{name}","Edit quota":"עריכת מיכסה","Edit subtitle":"עריכת כתובית","Enable":"הפעלה","Enable (%{count})":"הפעלה (%{count})","Enabled:":"פעילים:","Failed to change quota":"שינוי המכסה נכשל","Failed to change space subtitle":"שינוי כתובית המרחב נכשל","Failed to edit space description":"עריכת תיאור המרחב נכשלה","Failed to rename space":"שינוי שם המרחב נכשל","Failed to set space description":"הגדרת תיאור המרחב נכשל","File could not be located":"לא ניתן היה למצוא קובץ","Items per page":"פריטים לעמוד","Last activity":"פעילות אחרונה","Manager":"הנהלה","Members":"חברים","No changes":"אין שינויים","No restriction":"אין מגבלה","Overview of the information about the selected space":"סקירת פרטים על המרחב הנבחר","Please enter only numbers":"נא למלא מספרים בלבד","Quota":"מיכסה","Rename":"שינוי שם","Rename space":"שינוי שם מרחב","Revert":"שחזור","Save":"שמירה","Set as space description":"הגדרה כתיאור המרחב","Show":"הצגה","Show context menu":"הצגת תפריט הקשר","Show file extensions":"הצגת סיומות הקבצים","Show hidden files":"הצגת קבצים נסתרים","Space description":"תיאור המרחב","Space description was edited successfully":"תיאור המרחב נערך בהצלחה","Space description was set successfully":"תיאור המרחב הוגדר בהצלחה","Space name":"שם המרחב","Space name cannot be empty":"שם המרחב לא יכול להישאר ריק","Space name was changed successfully":"שם המרחב נערך בהצלחה","Space subtitle":"כתובית מרחב","Space subtitle was changed successfully":"כתובית המרחב נערכה בהצלחה","Subtitle":"כתובית","This space has %{memberShareCount} member.":["במרחב זה יש חבר אחד","במרחב זה יש %{memberShareCount} חברים","במרחב זה יש %{memberShareCount} חברים","במרחב זה יש %{memberShareCount} חברים"],"Unsaved changes":"שינויים שלא נשמרו"},"it":{"(Opens in new window)":"(Apre in una nuova finestra)","Cancel":"Cancella","Close":"Chiudi","Confirm":"Conferma","Delete":"Elimina","Details":"Dettagli","Download failed":"Download fallito","Edit quota":"Modifica quota","File could not be located":"Il file non è stato trovato","Manager":"Manager","Members":"Membri","No restriction":"Nessuna restrizione","Quota":"Quota","Show context menu":"Visualizza menu contestuale"},"pl":{"%{linkShareCount} link giving access.":["%{linkShareCount} link nadający dostęp.","%{linkShareCount} linki nadające dostęp.","%{linkShareCount} linków nadających dostęp.","%{linkShareCount} linków nadających dostęp."],"Cancel":"Anuluj","Change quota":"Zmień limit","Close":"Zamknij","Confirm":"Potwierdź","Delete":"Usuń","Details":"Szczegóły","Download failed":"Pobieranie nieudane","Edit description":"Edytuj opis","Edit quota":"Zmień limit","File could not be located":"Nie znaleziono pliku","Manager":"Zarządca","Members":"Członkowie","No changes":"Brak zmian","Open link list in share panel":"Otwórz listę linków w panelu udostępniania","Open member list in share panel":"Otwórz listę uczestników w panelu udostępniania","Open share panel":"Otwórz panel udostępniania","Please enter a value equal to or less than %{ quotaLimit }":"Podaj wartość równą lub mniejszą niż %{ quotaLimit }","Quota":"Limit","Remaining quota:":"Pozostałe dostępne miejsce:","Rename":"Zmień nazwę","Save":"Zapisz","Show":"Pokaż","Total quota:":"Całkowite dostępne miejsce:","Unsaved changes":"Niezapisane zmiany","Used quota:":"Użyte miejsce:"},"ru":{"Back to %{panel} panel":"Назад к %{panel} панели","Cancel":"Отменить","Changes saved":"Изменения сохранены","Close":"Закрыть","Close file sidebar":"Закрыть боковую панель","Confirm":"Подтвердить","Delete":"удалить","Details":"Подробности","Download failed":"Не удалась скачать","Manager":"Менеджер","Members":"Члены","No changes":"Без изменений","No restriction":"Без ограничений","Please enter only numbers":"Пожалуйста, введите только цифры","Quota":"Квота","Rename":"Переименование","Revert":"Вернуться","Save":"Сохранить","Unsaved changes":"Несохраненные изменения"},"sk":{"(Opens in new window)":"(Otvorí v novom okne)","Cancel":"Zrušiť","Delete":"Odstrániť","Details":"Podrobnosti","Download failed":"Stiahnutie zlyhalo","File could not be located":"Súbor sa nenašiel","No changes":"Žiadne zmeny","Revert":"Vrátiť","Save":"Uložiť","Unsaved changes":"Neuložené zmeny"},"sq":{"(Opens in new window)":"(Hapet në dritare të re)","%{ itemCount } space selected":["%{ itemCount } hapësirë e përzgjedhur","%{ itemCount } hapësia të përzgjedhura"],"%{displayName} (me)":"%{displayName} (unë)","%{linkShareCount} link giving access.":["%{linkShareCount} lidhje që lejon hyrje.","%{linkShareCount} lidhje që lejojnë hyrje."],"%{used} of %{total} used (%{percentage}% used)":"%{used} nga %{total} të përdorur (%{percentage}% të përdorur)","%{used} used (no restriction)":"%{used} të përdorur (pa kufizim)","Are you sure you want to delete the selected space?":["Jeni i sigurt se doni të fshihet hapësira e përzgjedhur?","Jeni i sigurt se doni të fshihen %{count} hapësirat e përzgjedhura?"],"Back to %{panel} panel":"Mbrapsht te paneli %{panel}","Cancel":"Anuloje","Change quota":"Ndryshoni kuota","Change quota for %{count} Spaces":"Ndryshoni kuota për %{count} Hapësira","Change quota for %{count} users":"Ndryshoni kuota për %{count} përdorues","Change quota for Space \"%{name}\"":"Ndryshoni kuota për Hapësirën “%{name}”","Change quota for user \"%{name}\"":"Ndryshoni kuota për përdoruesin “%{name}”","Change subtitle for space":"Ndryshoni nëntitullin për hapësirën","Changes saved":"Ndryshimet u ruajtën","Close":"Mbylle","Close file sidebar":"Mbyll anështyllë kartelash","Confirm":"Ripohojeni","Delete":"Fshije","Delete (%{count})":"Fshije (%{count})","Delete Space \"%{space}\"?":["Të fshihet Hapësira “%{space}”?","Të fshihen %{spaceCount} Hapësira?"],"Details":"Hollësi","Disable":"Çaktivizoje","Disable (%{count})":"Çaktivizoje (%{count})","Disable Space \"%{space}\"?":["Të çaktivizohet Hapësira “%{space}”?","Të çaktivizohen %{spaceCount} Hapësira?"],"Disabled:":"E çaktivizuar:","Display customization options of the files list":"Shfaq mundësi përshtatjeje të listës së kartelave","Download failed":"Shkarkimi dështoi","Edit description":"Përpunoni përshkrimin","Edit description for space %{name}":"Përpunoni përshkrim për hapësirën %{name}","Edit quota":"Përpunoni kuotë","Edit subtitle":"Përpunoni nëntitull","Enable":"Aktivizoje","Enable (%{count})":"Aktivizo (%{count})","Enable Space \"%{space}\"?":["Të aktivizohet Hapësira “%{space}”?","Të aktivizohen %{spaceCount} Hapësira?"],"Enabled:":"E aktivizuar:","Failed to change quota":"S’u arrit të ndryshohen kuota","Failed to change space quota":["S’u arrit të ndryshohen kuota hapësire","S’u arrit të ndryshohen kuota për %{count} hapësira"],"Failed to change space subtitle":"S’u arrit të ndryshohet nëntitull hapësire","Failed to change user quota":["S’u arrit të ndryshohen kuota përdoruesi","S’u arrit të ndryshohen kuota për %{count} përdorues"],"Failed to delete space %{spaceName}":"S’u arrit të fshihet hapësira %{spaceName}","Failed to disable space %{spaceName}":"S’u arrit të çaktivizohet hapësira %{spaceName}","Failed to edit space description":"S’u arrit të përpunohet përshkrim hapësire","Failed to rename space":"S’u arrit të riemërtohet hapësirë","Failed to restore space %{spaceName}":"S’u arrit të rikthehet hapësira %{spaceName}","Failed to set space description":"S’u arrit të ujdiset përshkrim hapësire","File could not be located":"S’u gjet dot kartela","Filter list":"Filtroni listën","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Nëse çaktivizoni hapësirën e përzgjedhur, s’mund të përdoret \nmë. Vetëm përgjegjës Hapësirash do të mund të hyjnë në të. Shënim: S’do të fshihet ndonjë kartelë nga shërbyesi.","Nëse çaktivizoni %{count} hapësirat e përzgjedhura, s’mund të përdoren më. Vetëm përgjegjës Hapësirash do të mund të hyjnë në to. Shënim: S’do të fshihet ndonjë kartelë nga shërbyesi."],"If you enable the selected space, it can be accessed again.":["Nëse aktivizoni hapësirën e përzgjedhur, ajo mund të ripërdoret","Nëse aktivizoni %{count} hapësira të përzgjedhura, ato mund të ripërdoren."],"Items per page":"Objekte për faqe","Last activity":"Veprimtaria e fundit","Manager":"Përgjegjës","Members":"Anëtarë","No changes":"Pa ndryshime","No restriction":"Pa kufizim","Open link list in share panel":"Hap listë lidhjesh te panel ndarjeje me të tjerët","Open member list in share panel":"Hap listë anëtarësh te panel ndarjeje me të tjerët","Open share panel":"Hap panel ndarjeje me të tjerë","Overview of the information about the selected space":"Përmbledhje e hollësive mbi hapësirën e përzgjedhur","Overview of the information about the selected spaces":"Përmbledhje hollësish mbi hapësirat e përzgjedhura","Please enter a value equal to or less than %{ quotaLimit }":"Ju lutemi, jepni një vlerë baras me ose më të vogël se %{ quotaLimit }","Please enter only numbers":"Ju lutemi, jepni vetëm numra","Quota":"Kuotë","Quota was changed successfully":"Kuotat u ndryshuan me sukses","Remaining quota:":"Kuota të paplotësuara","Rename":"Riemërtoje","Rename space":"Riemërtoni hapësirën","Revert":"Prapaktheje","Save":"Ruaje","Select a space to view details":"Që t’i shihni hollësitë, përzgjidhni një hapësirë","Set as space description":"Caktoni një përshkrim hapësire","Show":"Shfaqe","Show context menu":"Shfaq menu konteksti","Show file extensions":"Shfaq zgjatime kartelash","Show hidden files":"Shfaq kartela të fshehura","Space description":"Përshkrim hapësire","Space description was edited successfully":"Përshkrimi i hapësirë u përpunua me sukses","Space description was set successfully":"Përshkrimi i hapësirë u caktua me sukses","Space name":"Emër hapësire","Space name cannot be empty":"Emri i hapësirë s’mund të jetë i zbrazët","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Emri i hapësirës s’mund të përmbajë shenjat vijuese: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Emri i hapësirës s’mund të tejkalojë 255 shenja","Space name was changed successfully":"Emri i hapësirë u ndryshua me sukses","Space quota was changed successfully":["Kuotat e hapësirës u ndryshuan me sukses","Kuotat për %{count} hapësira u ndryshuan me sukses"],"Space subtitle":"Nëntitull hapësire","Space subtitle was changed successfully":"Nëntitulli i hapësirës u ndryshua me sukses","Space was deleted successfully":["Hapësira u fshi me sukses","Hapësirat u fshinë me sukses"],"Space was disabled successfully":["Hapësira u çaktivizua me sukses","Hapësirat u çaktivizuan me sukses"],"Space was enabled successfully":["Hapësira u aktivizua me sukses","Hapësirat u aktivizuan me sukses"],"Subtitle":"Nëntitull","Switch to condensed table view":"Kalo te pamje e ngjeshur e tabelës","Switch to default table view":"Kalo te pamje parazgjedhje e tabelës","Switch to tiles view":"Kalo te pamja me kuadrate","This space has %{memberShareCount} member.":["Kjo hapësirë ka %{memberShareCount} anëtar.","Kjo hapësira ka %{memberShareCount} anëtarë."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Kjo hapësirë ka %{memberShareCount} anëtarë dhe %{linkShareCount} lidhje.","This space has %{memberShareCount} members and one link.":"Kjo hapësirë ka %{memberShareCount} anëtarë dhe një lidhje.","This space has one member and %{linkShareCount} link.":["Kjo hapësirë ka një anëtar dhe %{linkShareCount} lidhje.","Kjo hapësirë ka një anëtar dhe %{linkShareCount} lidhje."],"Tile size":"Madhësi kuadrati","Total quota:":"Kuota gjithsej:","Unsaved changes":"Ndryshime të paruajtura","Used quota:":"Kuota të përdorura:","User quota was changed successfully":["Kuotat e përdoruesit u ndryshuan me sukses","Kuotat për %{count} përdorues u ndryshuan me sukses"]},"tr":{"(Opens in new window)":"(Yeni pencerede açılır)","%{displayName} (me)":"%{displayName} (ben)","%{linkShareCount} link giving access.":["%{linkShareCount} bağlantı erişim sağlıyor.","%{linkShareCount} bağlantı erişim sağlıyor."],"%{used} of %{total} used (%{percentage}% used)":"%{used} / %{total} kullanıldı (%{percentage}% kullanıldı)","%{used} used (no restriction)":"%{used} kullanıldı (kısıtlama yok)","Back to %{panel} panel":"%{panel} paneline geri dön","Cancel":"İptal","Change subtitle for space":"Alan için alt başlığı değiştir","Changes saved":"Değişiklikler kaydedildi","Close":"Kapat","Close file sidebar":"Yan çubuğu kapat","Confirm":"Onayla","Delete":"Sil","Details":"Detaylar","Disable":"Devre dışı bırak","Display customization options of the files list":"Dosya listesinin özelleştirme seçeneklerini görüntüleyin","Download failed":"İndirme başarısız","Edit description":"Açıklamayı düzenle","Edit description for space %{name}":"%{name} alanı için açıklamayı düzenle","Edit quota":"Kotayı düzenle","Edit subtitle":"Alt başlığı düzenle","Enable":"Etkinleştir","Failed to change space subtitle":"Alan alt başlığı değiştirilemedi","Failed to edit space description":"Alan açıklaması düzenlenemedi","Failed to rename space":"Alan yeniden adlandırılamadı","Failed to set space description":"Alan açıklaması ayarlanamadı","File could not be located":"Dosya bulunamadı","Items per page":"Sayfa başına öge","Last activity":"Son aktivite","Manager":"Yönetici","Members":"Üyeler","No changes":"Değişiklik yok","No restriction":"Kısıtlama yok","Open link list in share panel":"Bağlantı listesini paylaşım panelinde aç","Open member list in share panel":"Üye listesini paylaşım panelinde aç","Open share panel":"Paylaşım panelini aç","Overview of the information about the selected space":"Seçilen alan hakkındaki bilgilere genel bakış","Please enter only numbers":"Lütfen sadece sayı girin","Quota":"Kota","Rename":"Yeniden adlandır","Rename space":"Alanı yeniden adlandır","Revert":"Geri al","Save":"Kaydet","Set as space description":"Alan açıklaması olarak ayarla","Show":"Göster","Show context menu":"İçerik menüsünü göster","Show file extensions":"Dosya uzantılarını göster","Show hidden files":"Gizli dosyaları göster","Space description":"Alan açıklaması","Space description was edited successfully":"Alan açıklaması başarıyla düzenlendi","Space description was set successfully":"Alan açıklaması başarıyla ayarlandı","Space name":"Alan adı","Space name cannot be empty":"Alan adı boş olamaz","Space name was changed successfully":"Alan adı başarıyla değiştirildi","Space subtitle":"Alan alt başlığı","Space subtitle was changed successfully":"Alan alt başlığı başarıyla değiştirildi","Subtitle":"Alt başlık","This space has %{memberShareCount} member.":["Bu alanda %{memberShareCount} üye var.","Bu alanda %{memberShareCount} üye var."],"This space has one member and %{linkShareCount} link.":["Bu alanın bir üyesi ve %{linkShareCount} bağlantısı var.","Bu alanın bir üyesi ve %{linkShareCount} bağlantısı var."],"Unsaved changes":"Kaydedilmeyen değişiklikler"}} \ No newline at end of file +{"ar":{"Cancel":"إلغاء","Close":"غلق","Confirm":"تأكيد","Delete":"حذف","Details":"التفاصيل","Download failed":"فشل التحميل ","Edit quota":"تعديل الحصة النسبية","File could not be located":"تعذر تحديد موقع الملف","Manager":"مدير","Members":"اعضاء","No restriction":"لا قيود","Quota":"الحصه النسبيه","Rename":"إعادة تسمية","Save":"حفظ","Show context menu":"إظهار قائمة السياق"},"bg":{"(Opens in new window)":"(Отваря се в нов прозорец)","%{ itemCount } space selected":["%{ itemCount } избрано пространство","%{ itemCount } избрани пространства"],"%{displayName} (me)":"%{displayName} (аз)","%{linkShareCount} link giving access.":["%{linkShareCount} връзка, даваща достъп.","%{linkShareCount} връзки, даващи достъп."],"%{used} of %{total} used (%{percentage}% used)":"%{used} от %{total} използвани (%{percentage}% used)","%{used} used (no restriction)":"%{used} използвани (без ограничение)","Are you sure you want to delete the selected space?":["Сигурни ли сте, че искате да изтриете избраното пространство?","Сигурни ли сте, че искате да изтриете %{count} избрани пространства?"],"Back to %{panel} panel":"Назад към панела %{panel}","Cancel":"Отказ","Change quota":"Промяна на квота","Change quota for %{count} Spaces":"Промяна на квотата за %{count} Пространства","Change quota for %{count} users":"Промяна на квотата за %{count} потребителя","Change quota for Space \"%{name}\"":"Промяна на квотата за Пространството \"%{name}\"","Change quota for user \"%{name}\"":"Промяна на квотата за потребителя \"%{name}\"","Change subtitle for space":"Промяна на подзаглавието за пространството","Changes saved":"Промените са запазени","Close":"Затваряне","Close file sidebar":"Затваряне на страничната лента на файла","Confirm":"Потвърждаване","Delete":"Изтриване","Delete (%{count})":"Изтриване на (%{count})","Delete Space \"%{space}\"?":["Изтриване на Пространството \"%{space}\"?","Изтриване на %{spaceCount} Пространства?"],"Details":"Подробности","Disable":"Деактивиране","Disable (%{count})":"Деактивиране на (%{count})","Disable Space \"%{space}\"?":["Деактивиране на Пространството \"%{space}\"?","Деактивиране на %{spaceCount} Пространства?"],"Disabled:":"Деактивирано:","Display customization options of the files list":"Показване на опциите за персонализиране на списъка с файлове","Download failed":"Неуспешно изтегляне","Edit description":"Редактиране на описание","Edit description for space %{name}":"Редактиране на описанието на пространството %{name}","Edit quota":"Редактиране на квота","Edit subtitle":"Редактиране на подзаглавие","Enable":"Активиране","Enable (%{count})":"Активиране на (%{count})","Enable Space \"%{space}\"?":["Активиране на Пространството \"%{space}\"?","Активиране на %{spaceCount} Пространства?"],"Enabled:":"Активирано:","Failed to change quota":"Неуспех при промяна на квотата","Failed to change space quota":["Неуспешна промяна на квотата за пространството","Неуспешна промяна на квотата за %{count} пространства"],"Failed to change space subtitle":"Неуспешна промяна на подзаглавието на пространството","Failed to change user quota":["Неуспешна промяна на квотата за потребителя","Неуспешна промяна на квотата за %{count} потребителя"],"Failed to edit space description":"Неуспешно редактиране на описанието на пространството","Failed to rename space":"Неуспех при преименуването на пространство","Failed to set space description":"Неуспешно задаване на описание на пространството","File could not be located":"Файлът не може да бъде намерен","Filter list":"Списък с филтри","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Ако деактивирате избраното пространство, достъпът до него вече няма да е възможен. Само мениджърите на пространството ще имат достъп до него. Забележка: Никакви файлове няма да бъдат изтрити от сървъра.","Ако деактивирате избраните %{count} пространства, те вече няма да могат да бъдат достъпни. Само мениджърите на пространства все още ще имат достъп. Забележка: Никакви файлове няма да бъдат изтрити от сървъра."],"If you enable the selected space, it can be accessed again.":["Ако активирате избраното пространство, то ще бъде отново достъпно.","Ако активирате избраните пространства %{count}, те ще бъдат отново достъпни."],"Items per page":"Елемента на страница","Last activity":"Последна активност","Manager":"Мениджър","Members":"Членове","No changes":"Без промени","No restriction":"Без ограничение","Open link list in share panel":"Отваряне на списъка с връзки в панела за споделяне","Open member list in share panel":"Отваряне на списъка с членове в панела за споделяне","Open share panel":"Отваряне на панела за споделяне","Overview of the information about the selected space":"Преглед на информацията за избраното пространство","Overview of the information about the selected spaces":"Преглед на информацията за избраните пространства","Please enter a value equal to or less than %{ quotaLimit }":"Моля въведете стойност, равна или по-малка от %{ quotaLimit }","Please enter only numbers":"Моля, въведете само числа","Quota":"Квота","Quota was changed successfully":"Квотата е променена успешно","Remaining quota:":"Оставаща квота:","Rename":"Преименуване","Rename space":"Преименуване на пространството","Revert":"Връщане","Save":"Запазване","Select a space to view details":"Изберете пространство, за да видите подробности","Set as space description":"Задаване като описание на пространството","Show":"Показване","Show context menu":"Показване на контекстното меню","Show file extensions":"Показване на файловите разширения","Show hidden files":"Показване на скрити файлове","Sort by %{ name }":"Подреждане по %{ name }","Space description":"Описание на пространството","Space description was edited successfully":"Описанието на пространството е успешно редактирано","Space description was set successfully":"Описанието на пространството е успешно зададено","Space name":"Име на пространството","Space name cannot be empty":"Името на пространството не може да бъде празно","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Името на пространството не може да съдържа следните символи: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Името на пространството не може да надвишава 255 символа","Space name was changed successfully":"Името на пространството е успешно променено","Space quota was changed successfully":["Квотата на пространството е променена успешно","Квотата на %{count} пространства е променена успешно"],"Space subtitle":"Подзаглавие на пространство","Space subtitle was changed successfully":"Подзаглавието на пространството е успешно променено","Subtitle":"Подзаглавие","Switch to condensed table view":"Превключване към съкратен изглед на таблицата","Switch to default table view":"Превключване към изглед по подразбиране на таблицата","Switch to tiles view":"Превключване към изглед с плочки","This space has %{memberShareCount} member.":["Това пространство има %{memberShareCount} член.","Това пространство има %{memberShareCount} члена."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Това пространство има %{memberShareCount} члена и %{linkShareCount} връзки.","This space has %{memberShareCount} members and one link.":"Това пространство има %{memberShareCount} члена и една връзка.","This space has one member and %{linkShareCount} link.":["Това пространство има един член и %{linkShareCount} връзка.","Това пространство има един член и %{linkShareCount} връзки."],"Tile size":"Размер на плочките","Toggle selection":"Превключване на избора","Total quota:":"Обща квота:","Unsaved changes":"Промените нe са запазени","Used quota:":"Използвана квота:","User quota was changed successfully":["Квотата на потребителя е променена успешно","Квотата на %{count} потребителя е променена успешно"]},"cs":{"(Opens in new window)":"(Otevře se v novém okně)","%{ itemCount } space selected":["%{ itemCount } místo vybráno","%{ itemCount } místa vybrána","%{ itemCount } míst vybráno","%{ itemCount } míst vybráno"],"%{displayName} (me)":"%{displayName} (já)","%{linkShareCount} link giving access.":["%{linkShareCount} odkaz zprostředkovávající přistup.","%{linkShareCount} odkazy zprostředkovávající přistup.","%{linkShareCount} odkazů zprostředkovávajících přistup.","%{linkShareCount} odkazů zprostředkovávajících přistup."],"%{used} of %{total} used (%{percentage}% used)":"%{used} z %{total} využito (%{percentage}% využito)","%{used} used (no restriction)":"%{used} využito (bez omezení)","Are you sure you want to delete the selected space?":["Opravdu smazat vybrané místo?","Opravdu smazat %{count} vybraná místa?","Opravdu smazat %{count} vybraných míst?","Opravdu smazat %{count} vybraných míst?"],"Back to %{panel} panel":"Zpátky k %{panel} panelu","Cancel":"Zrušit","Change quota":"Změnit kvótu","Change quota for %{count} Spaces":"Změnit kvótu pro %{count} míst","Change quota for %{count} users":"Změnit kvótu pro %{count} uživatelů ","Change quota for Space \"%{name}\"":"Změnit kvótu pro místo \"%{name}\"","Change quota for user \"%{name}\"":"Změnit kvótu pro uživatele \"%{name}\"","Change subtitle for space":"Změnit podtitulek pro místo","Changes saved":"Změny uloženy","Close":"Zavřít","Close file sidebar":"Zavřít postranní panel souboru","Confirm":"Potvrdit","Delete":"Odstranit","Delete (%{count})":"Smazat (%{count})","Delete Space \"%{space}\"?":["Smazat %{spaceCount} místo?","Smazat %{spaceCount} místa?","Smazat %{spaceCount} míst?","Smazat %{spaceCount} míst?"],"Details":"Detaily","Disable":"Deaktivovat","Disable (%{count})":"Deaktivovat (%{count})","Disable Space \"%{space}\"?":["Deaktivovat místo \"%{space}\"?","Deaktivovat %{spaceCount} místa?","Deaktivovat %{spaceCount} míst?","Deaktivovat %{spaceCount} míst?"],"Disabled:":"Deaktivováno:","Download failed":"Stahování selhalo","Edit description":"Změnit popisek","Edit description for space %{name}":"Změnit popisek místa %{name}","Edit quota":"Změnit kvótu","Edit subtitle":"Změnit titulek","Enable":"Povolit","Enable (%{count})":"Aktivovat (%{count})","Enable Space \"%{space}\"?":["Aktivovat místo \"%{space}\"?","Aktivovat %{spaceCount} místa?","Aktivovat %{spaceCount} míst?","Aktivovat %{spaceCount} míst?"],"Enabled:":"Povoleno:","Failed to change quota":"Nezdařilo se změnit kvótu","Failed to change space quota":["Nezdařilo se změnit kvótu místa","Nezdařilo se změnit kvótu pro %{count} místa","Nezdařilo se změnit kvótu pro %{count} míst","Nezdařilo se změnit kvótu pro %{count} míst"],"Failed to change space subtitle":"Nezdařilo se změnit podtitulek místa","Failed to change user quota":["Nezdařilo se změnit kvótu uživatele","Nezdařilo se změnit kvótu pro %{count} uživatele","Nezdařilo se změnit kvótu pro %{count} uživatelů","Nezdařilo se změnit kvótu pro %{count} uživatelů"],"Failed to edit space description":"Nezdařilo se editovat popisek místa","Failed to rename space":"Nezdařilo se přejmenovat místo","Failed to set space description":"Nezdařilo se nastavit popisek místa","File could not be located":"Soubor se nepodařilo najít","Filter list":"Seznam filtrů","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Pokud vybrané místo bude deaktivováno, nebude nadále přístupné. Pouze Manažeři místa budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru.","Pokud %{count} vybraná místa budou deaktivována, nebudou nadále přístupná. Pouze Manažeři míst budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru.","Pokud %{count} vybraných míst bude deaktivováno, nebudou nadále přístupná. Pouze Manažeři míst budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru.","Pokud %{count} vybraných míst bude deaktivováno, nebudou nadále přístupná. Pouze Manažeři míst budou stále mít přístup. Poznámka: Žádné soubory nebudou smazány ze serveru."],"If you enable the selected space, it can be accessed again.":["Pokud bude vybrané místo aktivováno, bude možno k němu opět přistupovat.","Pokud budou %{count} vybraná místa aktivována, bude možno k nim opět přistupovat.","Pokud bude %{count} vybraných míst aktivováno, bude možno k nim opět přistupovat.","Pokud bude %{count} vybraných míst aktivováno, bude možno k nim opět přistupovat."],"Last activity":"Poslední aktivita","Manager":"Manažer","Members":"Členové","No changes":"Žádné změny","No restriction":"Žádná omezení","Open link list in share panel":"Otevřít seznam odkazů v panelu sdílení ","Open member list in share panel":"Otevřít seznam členů v panelu sdílení","Open share panel":"Otevřít panel sdílení","Overview of the information about the selected space":"Přehled informací o vybraném místě","Overview of the information about the selected spaces":"Přehled informací o vybraných místech","Please enter a value equal to or less than %{ quotaLimit }":"Prosím vložte hodnotu menší nebo rovnou %{ quotaLimit }","Please enter only numbers":"Prosím vkládejte pouze čísla","Quota":"Kvóta","Quota was changed successfully":"Kvóta byla změněna úspěšně","Remaining quota:":"Zbývající kvóta:","Rename":"Přejmenovat","Rename space":"Přejmenovat místo","Revert":"Vrátit zpět","Save":"Uložit","Select a space to view details":"Vybrat místo pro prohlížení detailů","Set as space description":"Nastavit jako popisek místa","Show":"Ukázat","Show context menu":"Ukázat kontextovou nabídku","Sort by %{ name }":"Seřadit podle %{ name }","Space description":"Popisek místa","Space description was edited successfully":"Popisek místa byl úspěšně editován","Space description was set successfully":"Popisek místa byl úspěšně nastaven","Space name":"Název místa","Space name cannot be empty":"Název místa nesmí být prázdný","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Název místa nesmí obsahovat následující znaku: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Název místa nesmí překročit 255 znaků","Space name was changed successfully":"Název místa byl úspěšně změněn","Space quota was changed successfully":["Kvóta místa byla změněna úspěšně","Kvóta %{count} míst byla změněna úspěšně","Kvóta %{count} míst byla změněna úspěšně","Kvóta %{count} míst byla změněna úspěšně"],"Space subtitle":"Podtitulek místa","Space subtitle was changed successfully":"Podtitulek místa byl změněn úspěšně","Subtitle":"Podtitulek","This space has %{memberShareCount} member.":["Toto místo má %{memberShareCount} člena.","Toto místo má %{memberShareCount} členy.","Toto místo má %{memberShareCount} členů.","Toto místo má %{memberShareCount} členů."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Toto místo má %{memberShareCount} členů a %{linkShareCount} odkazů.","This space has %{memberShareCount} members and one link.":"Toto místo má %{memberShareCount} členů a jeden odkaz.","This space has one member and %{linkShareCount} link.":["Toto místo má jednoho člena a %{linkShareCount} odkaz.","Toto místo má jednoho člena a %{linkShareCount} odkazy.","Toto místo má jednoho člena a %{linkShareCount} odkazů.","Toto místo má jednoho člena a %{linkShareCount} odkazů."],"Toggle selection":"Přepínací výběr","Total quota:":"Celková kvóta:","Unsaved changes":"Neuložené změny","Used quota:":"Využitá kvóta:","User quota was changed successfully":["Kvóta uživatele byla změněna úspěšně","Kvóta %{count} uživatelů byla změněna úspěšně","Kvóta %{count} uživatelů byla změněna úspěšně","Kvóta %{count} uživatelů byla změněna úspěšně"]},"de":{"(Opens in new window)":"(Wird in neuem Fenster geöffnet)","%{ itemCount } space selected":["%{ itemCount } spaces ausgewählt","%{ itemCount } Spaces ausgewählt"],"%{displayName} (me)":"%{displayName} (ich)","%{linkShareCount} link giving access.":["%{linkShareCount} Link gewährt Zugriff.","%{linkShareCount} Links gewähren Zugriff."],"%{spaceCount} space was deleted successfully":["%{spaceCount} Space wurde erfolgreich gelöscht.","%{spaceCount} Spaces wurden erfolgreich gelöscht."],"%{spaceCount} space was disabled successfully":["%{spaceCount} Space wurden erfolgreich deaktiviert.","%{spaceCount} Spaces wurden erfolgreich deaktiviert."],"%{spaceCount} space was enabled successfully":["%{spaceCount} Space wurden erfolgreich aktiviert.","%{spaceCount} Spaces wurden erfolgreich aktiviert."],"%{used} of %{total} used (%{percentage}% used)":"%{used} von %{total} benutzt (%{percentage}% verwendet)","%{used} used (no restriction)":"%{used} benutzt (unbegrenzt)","All Files":"Alle Dateien","Are you sure you want to delete the selected space?":["Soll der ausgewählte Space wirklich gelöscht werden?","Sollen %{count} ausgewählte Spaces wirklich gelöscht werden?"],"Back to %{panel} panel":"Zurück zum %{panel} Panel","Cancel":"Abbrechen","Change quota":"Quota ändern","Change quota for %{count} Spaces":"Quota für %{count} Spaces ändern","Change quota for %{count} users":"Quota für %{count} Personen ändern","Change quota for Space \"%{name}\"":"Quota für den Space %{name} ändern","Change quota for user \"%{name}\"":"Ändere Quota für Person \"%{name}\"","Change subtitle for space":"Untertitel für diesen Space ändern","Changes saved":"Änderungen gespeichert","Close":"Schließen","Close file sidebar":"Seitenleiste schließen","Confirm":"Bestätigen","Current Folder":"Aktueller Ordner","Delete":"Löschen","Delete (%{count})":"Lösche (%{count})","Delete Space \"%{space}\"?":["Space \"%{space}\" löschen?","%{spaceCount} Spaces löschen?"],"Details":"Details","Disable":"Deaktivieren","Disable (%{count})":"Deaktiviere (%{count})","Disable Space \"%{space}\"?":["Space \"%{space}\" deaktivieren?","%{spaceCount} Spaces deaktivieren?"],"Disabled:":"Deaktiviert","Display customization options of the files list":"Anpassungsoptionen für die Dateienliste anzeigen","Download failed":"Fehler beim Herunterladen","Edit description":"Beschreibung bearbeiten","Edit description for space %{name}":"Beschreibung für Space %{name} bearbeiten","Edit quota":"Quota ändern","Edit subtitle":"Untertitel bearbeiten","Enable":"Aktivieren","Enable (%{count})":"(%{count}) aktivieren","Enable Space \"%{space}\"?":["Space \"%{space}\" aktivieren?","%{spaceCount} Spaces aktivieren?"],"Enabled:":"Aktiviert","Failed to change quota":"Quota-Änderung fehlgeschlagen","Failed to change space quota":["Fehler beim Ändern der Space-Quota","Fehler beim Ändern der Quota für %{count} Spaces"],"Failed to change space subtitle":"Fehler beim Ändern des Space-Untertitels","Failed to change user quota":["Fehler beim Ändern der Benutzerquota","Fehler beim Ändern der Quota für %{count} Personen"],"Failed to delete %{spaceCount} space":["Löschen von %{spaceCount} Space fehlgeschlagen","Löschen von %{spaceCount} Spaces fehlgeschlagen"],"Failed to delete space \"%{space}\"":"Löschen von Space \"%{space}\" fehlgeschlagen","Failed to disable %{spaceCount} space":["Deaktivieren von %{spaceCount} Space fehlgeschlagen","Deaktivieren von %{spaceCount} Spaces fehlgeschlagen"],"Failed to disable space \"%{space}\"":"Dektivieren von Space \"%{space}\" fehlgeschlagen","Failed to edit space description":"Fehler beim Ändern der Space-Beschreibung","Failed to enable %{spaceCount} space":["Aktivieren von %{spaceCount} Space fehlgeschlagen","Aktivieren von %{spaceCount} Spaces fehlgeschlagen"],"Failed to enabled space \"%{space}\"":"Aktivieren von Space \"%{space}\" fehlgeschlagen","Failed to rename space":"Fehler beim Umbenennen des Space","Failed to set space description":"Fehler beim Speichern der Space-Beschreibung","File could not be located":"Datei konnte nicht gefunden werden","Filter list":"Liste filtern","Group By:":"Gruppieren nach:","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Wenn Sie den ausgewählten Space deaktivieren, kann auf ihn nicht mehr zugegriffen werden. Nur Space-Manager haben noch Zugriff.\nHinweis: Es werden keine Dateien von dem Server gelöscht.","Wenn Sie die %{count} ausgewählten Spaces deaktivieren, kann auf sie nicht mehr zugegriffen werden. Nur Space-Verwalter/-innen haben noch Zugriff.\nHinweis: Es werden keine Dateien von dem Server gelöscht."],"If you enable the selected space, it can be accessed again.":["Wenn Sie den ausgewählten Space aktivieren, kann auf ihn wieder zugegriffen werden.","Wenn Sie die %{count} ausgewählten Spaces aktivieren, kann auf sie wieder zugegriffen werden."],"Items per page":"Dateien pro Seite","Last activity":"Letzte Aktivitäten","Manager":"Manager/-in","Members":"Mitglieder","No changes":"Keine Änderungen","No restriction":"Unbegrenzt","Open link list in share panel":"Liste der Linkfreigaben in der Seitenleiste öffnen","Open member list in share panel":"Liste der Mitglieder in der Seitenleiste öffnen","Open share panel":"Geteilt-mit Bereich öffnen","Overview of the information about the selected space":"Alle Infos zum ausgewählten Space","Overview of the information about the selected spaces":"Alle Infos zum ausgewählten Space","Please enter a value equal to or less than %{ quotaLimit }":"Bitte geben Sie einen Wert ein, der gleich oder kleiner als %{ quotaLimit } ist.","Please enter only numbers":"Bitte nur Zahlen eingeben.","Please turn on pop-ups and redirects in your browser settings to make sure everything works right.":"Bitte schalten Sie Pop-ups und Umleitungen in Ihren Browser-Einstellungen ein, um sicherzustellen, dass alles korrekt funktioniert.","Pop-up and redirect block detected":"Pop-ups und Umleitungen werden geblockt.","Quota":"Quota","Quota was changed successfully":"Quota wurde erfolgreich geändert.","Remaining quota:":"Verbleibende Quota","Rename":"Umbenennen","Rename space":"Space umbenennen","Revert":"Zurücknehmen","Save":"Speichern","Select a space to view details":"Wählen Sie einen Space aus, um hier Details anzuzeigen.","Set as space description":"Als Space-Beschreibung festlegen","Show":"Anzeigen","Show context menu":"Kontextmenü anzeigen","Show file extensions":"Dateiendungen anzeigen","Show hidden files":"Versteckte Dateien anzeigen","Sort by %{ name }":"Nach %{ name } sortieren","Space \"%{space}\" was deleted successfully":"Space \"%{space}\" wurde erfolgreich gelöscht.","Space \"%{space}\" was disabled successfully":"Space \"%{space}\" wurde erfolgreich deaktiviert.","Space \"%{space}\" was enabled successfully":"Space \"%{space}\" wurde erfolgreich aktiviert.","Space description":"Space-Beschreibung","Space description was edited successfully":"Die Space-Beschreibung wurde erfolgreich bearbeitet.","Space description was set successfully":"Die Space-Beschreibung wurde erfolgreich festgelegt.","Space name":"Name des Spaces","Space name cannot be empty":"Der Name des Spaces darf nicht leer sein.","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Space-Namen dürfen folgende Zeichen nicht enthalten: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Der Space-Name darf nicht länger als 255 Zeichen sein.","Space name was changed successfully":"Der Space-Name wurde erfolgreich geändert.","Space quota was changed successfully":["Die Space-Quota wurde erfolgreich geändert.","Quota für %{count} Spaces wurde erfolgreich geändert."],"Space subtitle":"Space-Untertitel","Space subtitle was changed successfully":"Der Space-Untertitel wurde erfolgreich geändert.","Subtitle":"Untertitel","Switch to condensed table view":"Zur verdichteten Tabellenansicht wechseln","Switch to default table view":"Zur normalen Tabellenansicht wechseln","Switch to tiles view":"Zur Kachelansicht wechseln","This space has %{memberShareCount} member.":["Dieser Space hat %{memberShareCount} Mitglied.","Dieser Space hat %{memberShareCount} Mitglieder."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Dieser Space hat %{memberShareCount} Mitglieder und %{linkShareCount} Links.","This space has %{memberShareCount} members and one link.":"Dieser Space hat %{memberShareCount} Mitglieder und einen Link.","This space has one member and %{linkShareCount} link.":["Dieser Space hat ein Mitglied und %{linkShareCount} Link.","Dieser Space hat ein Mitglied und %{linkShareCount} Links."],"Tile size":"Kachelgröße","Toggle selection":"Auswahl umschalten","Total quota:":"Gesamtquota","Unsaved changes":"Ungespeicherte Änderungen","Used quota:":"Benutzte Quota","User quota was changed successfully":["Benutzerquota erfolgreich geändert","Quota für %{count} Personen wurde erfolgreich geändert."]},"es":{"(Opens in new window)":"(Abre en nueva ventana)","Cancel":"Cancelar","Change quota":"Cambiar cuota","Change quota for %{count} Spaces":"Cambiar la cuota para el espacio %{count}","Close":"Cerrar","Confirm":"Confirmar","Delete":"Eliminar","Delete (%{count})":"Eliminada (%{count})","Details":"Detalles","Disable (%{count})":"Deshabilitada (%{count})","Disabled:":"Deshabilitado","Download failed":"Descarga fallida","Edit quota":"Editar cuota","Enable (%{count})":"Habilitada (%{count})","Enabled:":"Habilitado","Failed to change quota":"Fallo al cambiar la cuota","Failed to change space subtitle":"Error al cambiar el subtítulo del espacio","Failed to edit space description":"Error al editar la descripción del espacio","Failed to rename space":"Error al renombrar el espacio","File could not be located":"No se ha podido encontrar el archivo","Filter list":"Filtrar lista","Items per page":"Elementos por página","Manager":"Gestor","Members":"Miembros","No restriction":"Sin restricciones","Quota":"Cuota","Remaining quota:":"Cuota restante","Rename":"Renombrar","Save":"Guardar","Show context menu":"Mostrar menú contextual","Total quota:":"Cuota total","Unsaved changes":"Cambios sin guardar","Used quota:":"Cuota utilizada"},"fr":{"(Opens in new window)":"(ouvre dans une nouvelle fenêtre)","%{displayName} (me)":"%{displayName} (Moi)","%{linkShareCount} link giving access.":["%{linkShareCount} lien donnant accès.","%{linkShareCount} liens donnant accès","%{linkShareCount} liens donnant accès"],"%{used} of %{total} used (%{percentage}% used)":"%{used} utilisé sur %{total} (%{percentage}% utilisée)","%{used} used (no restriction)":"%{used} utilisée (Pas de restriction)","Cancel":"Annuler","Change subtitle for space":"Changer les sous-titres de l''espace","Close":"Fermer","Close file sidebar":"Ouvrir le panneau latéral de fichier.","Confirm":"Confirmer","Delete":"Supprimer","Details":"Détails","Disable":"Désactiver","Display customization options of the files list":"Afficher les options de customisation de la liste de fichier","Download failed":"Echec de téléchargement","Edit description":"Modifier la description","Edit quota":"Modifier le quota","Edit subtitle":"Modifier les sous-titres","Enable":"Activer","File could not be located":"Le fichier n'a pas été trouvé","Items per page":"Éléments par page","Last activity":"Dernière activité","Manager":"Gestionnaire","Members":"Membres","No changes":"Aucune modification","No restriction":"Aucune restriction","Please enter only numbers":"Merci de ne saisir que des chiffres","Quota":"Quota","Rename":"Renommer","Revert":"Rétablir","Save":"Enregistrer","Show file extensions":"Afficher les extensions du fichier","Show hidden files":"Afficher les fichiers masqués","Sort by %{ name }":"trier par %{ name }","Subtitle":"Sous-titre","Unsaved changes":"Modifications non enregistrées"},"gl":{"Cancel":"Cancelar","Confirm":"Confirmar","Delete":"Eliminar","Download failed":"Produciuse un fallo na descarga","File could not be located":"Non foi posíbel localizar o ficheiro","Rename":"Renomear","Save":"Gardar"},"he":{"(Opens in new window)":"(נפתח בחלון חדש)","%{displayName} (me)":"%{displayName} (אני)","%{linkShareCount} link giving access.":["קישור אחד מעניק גישה.","%{linkShareCount} קישורים מעניקים גישה.","%{linkShareCount} קישורים מעניקים גישה.","%{linkShareCount} קישורים מעניקים גישה."],"%{used} of %{total} used (%{percentage}% used)":"%{used} מתוך %{total} מנוצלים (%{percentage}% מנוצלים)","%{used} used (no restriction)":"%{used} מנוצלים (אין הגבלה)","Cancel":"ביטול","Change quota":"שינוי מכסה","Change subtitle for space":"החלפת כתובית למרחב","Changes saved":"שינויים נשמרו","Close":"סגירה","Close file sidebar":"סגירת סרגל הצד של הקבצים","Confirm":"אישור","Delete":"מחיקה","Delete (%{count})":"מחיקה (%{count})","Details":"פרטים","Disable":"השבתה","Disable (%{count})":"השבתה (%{count})","Disabled:":"מושבתים:","Display customization options of the files list":"הצגת אפשרות כוונון לרשימת הקבצים","Download failed":"הורדה נכשלה","Edit description":"עריכת תיאור","Edit description for space %{name}":"עריכת תיאור המרחב %{name}","Edit quota":"עריכת מיכסה","Edit subtitle":"עריכת כתובית","Enable":"הפעלה","Enable (%{count})":"הפעלה (%{count})","Enabled:":"פעילים:","Failed to change quota":"שינוי המכסה נכשל","Failed to change space subtitle":"שינוי כתובית המרחב נכשל","Failed to edit space description":"עריכת תיאור המרחב נכשלה","Failed to rename space":"שינוי שם המרחב נכשל","Failed to set space description":"הגדרת תיאור המרחב נכשל","File could not be located":"לא ניתן היה למצוא קובץ","Items per page":"פריטים לעמוד","Last activity":"פעילות אחרונה","Manager":"הנהלה","Members":"חברים","No changes":"אין שינויים","No restriction":"אין מגבלה","Overview of the information about the selected space":"סקירת פרטים על המרחב הנבחר","Please enter only numbers":"נא למלא מספרים בלבד","Quota":"מיכסה","Rename":"שינוי שם","Rename space":"שינוי שם מרחב","Revert":"שחזור","Save":"שמירה","Set as space description":"הגדרה כתיאור המרחב","Show":"הצגה","Show context menu":"הצגת תפריט הקשר","Show file extensions":"הצגת סיומות הקבצים","Show hidden files":"הצגת קבצים נסתרים","Space description":"תיאור המרחב","Space description was edited successfully":"תיאור המרחב נערך בהצלחה","Space description was set successfully":"תיאור המרחב הוגדר בהצלחה","Space name":"שם המרחב","Space name cannot be empty":"שם המרחב לא יכול להישאר ריק","Space name was changed successfully":"שם המרחב נערך בהצלחה","Space subtitle":"כתובית מרחב","Space subtitle was changed successfully":"כתובית המרחב נערכה בהצלחה","Subtitle":"כתובית","This space has %{memberShareCount} member.":["במרחב זה יש חבר אחד","במרחב זה יש %{memberShareCount} חברים","במרחב זה יש %{memberShareCount} חברים","במרחב זה יש %{memberShareCount} חברים"],"Unsaved changes":"שינויים שלא נשמרו"},"it":{"(Opens in new window)":"(Apre in una nuova finestra)","Cancel":"Cancella","Close":"Chiudi","Confirm":"Conferma","Delete":"Elimina","Details":"Dettagli","Download failed":"Download fallito","Edit quota":"Modifica quota","File could not be located":"Il file non è stato trovato","Manager":"Manager","Members":"Membri","No restriction":"Nessuna restrizione","Quota":"Quota","Show context menu":"Visualizza menu contestuale","Sort by %{ name }":"Ordina per %{ name }"},"pl":{"%{linkShareCount} link giving access.":["%{linkShareCount} link nadający dostęp.","%{linkShareCount} linki nadające dostęp.","%{linkShareCount} linków nadających dostęp.","%{linkShareCount} linków nadających dostęp."],"All Files":"Wszystkie pliki","Cancel":"Anuluj","Change quota":"Zmień limit","Changes saved":"Zmiany zapisane","Close":"Zamknij","Confirm":"Potwierdź","Current Folder":"Bieżący katalog","Delete":"Usuń","Delete (%{count})":"Usuń (%{count})","Details":"Szczegóły","Download failed":"Pobieranie nieudane","Edit description":"Edytuj opis","Edit quota":"Zmień limit","File could not be located":"Nie znaleziono pliku","Filter list":"Lista filtrów","Manager":"Zarządca","Members":"Członkowie","No changes":"Brak zmian","Open link list in share panel":"Otwórz listę linków w panelu udostępniania","Open member list in share panel":"Otwórz listę uczestników w panelu udostępniania","Open share panel":"Otwórz panel udostępniania","Please enter a value equal to or less than %{ quotaLimit }":"Podaj wartość równą lub mniejszą niż %{ quotaLimit }","Quota":"Limit","Remaining quota:":"Pozostałe dostępne miejsce:","Rename":"Zmień nazwę","Save":"Zapisz","Show":"Pokaż","Sort by %{ name }":"Sortuj wg %{ name }","Total quota:":"Całkowite dostępne miejsce:","Unsaved changes":"Niezapisane zmiany","Used quota:":"Użyte miejsce:"},"ru":{"Back to %{panel} panel":"Назад к %{panel} панели","Cancel":"Отменить","Changes saved":"Изменения сохранены","Close":"Закрыть","Close file sidebar":"Закрыть боковую панель","Confirm":"Подтвердить","Delete":"удалить","Details":"Подробности","Download failed":"Не удалась скачать","Manager":"Менеджер","Members":"Члены","No changes":"Без изменений","No restriction":"Без ограничений","Please enter only numbers":"Пожалуйста, введите только цифры","Quota":"Квота","Rename":"Переименование","Revert":"Вернуться","Save":"Сохранить","Sort by %{ name }":"Сортировать по %{имя}","Unsaved changes":"Несохраненные изменения"},"sk":{"(Opens in new window)":"(Otvorí v novom okne)","Cancel":"Zrušiť","Delete":"Odstrániť","Details":"Podrobnosti","Download failed":"Stiahnutie zlyhalo","File could not be located":"Súbor sa nenašiel","No changes":"Žiadne zmeny","Revert":"Vrátiť","Save":"Uložiť","Unsaved changes":"Neuložené zmeny"},"sq":{"(Opens in new window)":"(Hapet në dritare të re)","%{ itemCount } space selected":["%{ itemCount } hapësirë e përzgjedhur","%{ itemCount } hapësia të përzgjedhura"],"%{displayName} (me)":"%{displayName} (unë)","%{linkShareCount} link giving access.":["%{linkShareCount} lidhje që lejon hyrje.","%{linkShareCount} lidhje që lejojnë hyrje."],"%{spaceCount} space was deleted successfully":["U fshi me sukses %{spaceCount} hapësirë","U fshinë me sukses %{spaceCount} hapësira"],"%{spaceCount} space was disabled successfully":["U çaktivizua me sukses %{spaceCount} hapësirë","U çaktivizuan me sukses %{spaceCount} hapësira"],"%{spaceCount} space was enabled successfully":["U aktivizua me sukses %{spaceCount} hapësirë","U aktivizuan me sukses %{spaceCount} hapësira"],"%{used} of %{total} used (%{percentage}% used)":"%{used} nga %{total} të përdorur (%{percentage}% të përdorur)","%{used} used (no restriction)":"%{used} të përdorur (pa kufizim)","All Files":"Krejt Kartelat","Are you sure you want to delete the selected space?":["Jeni i sigurt se doni të fshihet hapësira e përzgjedhur?","Jeni i sigurt se doni të fshihen %{count} hapësirat e përzgjedhura?"],"Back to %{panel} panel":"Mbrapsht te paneli %{panel}","Cancel":"Anuloje","Change quota":"Ndryshoni kuota","Change quota for %{count} Spaces":"Ndryshoni kuota për %{count} Hapësira","Change quota for %{count} users":"Ndryshoni kuota për %{count} përdorues","Change quota for Space \"%{name}\"":"Ndryshoni kuota për Hapësirën “%{name}”","Change quota for user \"%{name}\"":"Ndryshoni kuota për përdoruesin “%{name}”","Change subtitle for space":"Ndryshoni nëntitullin për hapësirën","Changes saved":"Ndryshimet u ruajtën","Close":"Mbylle","Close file sidebar":"Mbyll anështyllë kartelash","Confirm":"Ripohojeni","Current Folder":"Dosja e Tanishme","Delete":"Fshije","Delete (%{count})":"Fshije (%{count})","Delete Space \"%{space}\"?":["Të fshihet Hapësira “%{space}”?","Të fshihen %{spaceCount} Hapësira?"],"Details":"Hollësi","Disable":"Çaktivizoje","Disable (%{count})":"Çaktivizoje (%{count})","Disable Space \"%{space}\"?":["Të çaktivizohet Hapësira “%{space}”?","Të çaktivizohen %{spaceCount} Hapësira?"],"Disabled:":"E çaktivizuar:","Display customization options of the files list":"Shfaq mundësi përshtatjeje të listës së kartelave","Download failed":"Shkarkimi dështoi","Edit description":"Përpunoni përshkrimin","Edit description for space %{name}":"Përpunoni përshkrim për hapësirën %{name}","Edit quota":"Përpunoni kuotë","Edit subtitle":"Përpunoni nëntitull","Enable":"Aktivizoje","Enable (%{count})":"Aktivizo (%{count})","Enable Space \"%{space}\"?":["Të aktivizohet Hapësira “%{space}”?","Të aktivizohen %{spaceCount} Hapësira?"],"Enabled:":"E aktivizuar:","Failed to change quota":"S’u arrit të ndryshohen kuota","Failed to change space quota":["S’u arrit të ndryshohen kuota hapësire","S’u arrit të ndryshohen kuota për %{count} hapësira"],"Failed to change space subtitle":"S’u arrit të ndryshohet nëntitull hapësire","Failed to change user quota":["S’u arrit të ndryshohen kuota përdoruesi","S’u arrit të ndryshohen kuota për %{count} përdorues"],"Failed to delete %{spaceCount} space":["S’u arrit të fshihet %{spaceCount} hapësirë","S’u arrit të fshihen %{spaceCount} hapësira"],"Failed to delete space \"%{space}\"":"S’u arrit të fshihet hapësira “%{space}”","Failed to disable %{spaceCount} space":["S’u arrit të çaktivizohet %{spaceCount} hapësirë","S’u arrit të çaktivizohen %{spaceCount} hapësira"],"Failed to disable space \"%{space}\"":"S’u arrit të çaktivizohet hapësira “%{space}”","Failed to edit space description":"S’u arrit të përpunohet përshkrim hapësire","Failed to enable %{spaceCount} space":["S’u arrit të aktivizohet %{spaceCount} hapësirë","S’u arrit të aktivizohen %{spaceCount} hapësira"],"Failed to enabled space \"%{space}\"":"S’u arrit të aktivizohet hapësira “%{space}”","Failed to rename space":"S’u arrit të riemërtohet hapësirë","Failed to set space description":"S’u arrit të ujdiset përshkrim hapësire","File could not be located":"S’u gjet dot kartela","Filter list":"Filtroni listën","Group By:":"Grupoji Sipas:","If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server.":["Nëse çaktivizoni hapësirën e përzgjedhur, s’mund të përdoret \nmë. Vetëm përgjegjës Hapësirash do të mund të hyjnë në të. Shënim: S’do të fshihet ndonjë kartelë nga shërbyesi.","Nëse çaktivizoni %{count} hapësirat e përzgjedhura, s’mund të përdoren më. Vetëm përgjegjës Hapësirash do të mund të hyjnë në to. Shënim: S’do të fshihet ndonjë kartelë nga shërbyesi."],"If you enable the selected space, it can be accessed again.":["Nëse aktivizoni hapësirën e përzgjedhur, ajo mund të ripërdoret","Nëse aktivizoni %{count} hapësira të përzgjedhura, ato mund të ripërdoren."],"Items per page":"Objekte për faqe","Last activity":"Veprimtaria e fundit","Manager":"Përgjegjës","Members":"Anëtarë","No changes":"Pa ndryshime","No restriction":"Pa kufizim","Open link list in share panel":"Hap listë lidhjesh te panel ndarjeje me të tjerët","Open member list in share panel":"Hap listë anëtarësh te panel ndarjeje me të tjerët","Open share panel":"Hap panel ndarjeje me të tjerë","Overview of the information about the selected space":"Përmbledhje e hollësive mbi hapësirën e përzgjedhur","Overview of the information about the selected spaces":"Përmbledhje hollësish mbi hapësirat e përzgjedhura","Please enter a value equal to or less than %{ quotaLimit }":"Ju lutemi, jepni një vlerë baras me ose më të vogël se %{ quotaLimit }","Please enter only numbers":"Ju lutemi, jepni vetëm numra","Please turn on pop-ups and redirects in your browser settings to make sure everything works right.":"Ju lutemi, aktivizoni te rregullimet tuaja të shfletuesit flluskat dhe ridrejtimet, për të siguruar funksionimin në rregull të gjithçkaje.","Pop-up and redirect block detected":"U pikas bllokim flluskash dhe ridrejtimesh","Quota":"Kuotë","Quota was changed successfully":"Kuotat u ndryshuan me sukses","Remaining quota:":"Kuota të paplotësuara","Rename":"Riemërtoje","Rename space":"Riemërtoni hapësirën","Revert":"Prapaktheje","Save":"Ruaje","Select a space to view details":"Që t’i shihni hollësitë, përzgjidhni një hapësirë","Set as space description":"Caktoni një përshkrim hapësire","Show":"Shfaqe","Show context menu":"Shfaq menu konteksti","Show file extensions":"Shfaq zgjatime kartelash","Show hidden files":"Shfaq kartela të fshehura","Sort by %{ name }":"Renditi sipas %{ name }","Space \"%{space}\" was deleted successfully":"Hapësira “%{space}” u fshi me sukses","Space \"%{space}\" was disabled successfully":"Hapësira “%{space}” u çaktivizua me sukses","Space \"%{space}\" was enabled successfully":"Hapësira “%{space}” u aktivizua me sukses","Space description":"Përshkrim hapësire","Space description was edited successfully":"Përshkrimi i hapësirë u përpunua me sukses","Space description was set successfully":"Përshkrimi i hapësirë u caktua me sukses","Space name":"Emër hapësire","Space name cannot be empty":"Emri i hapësirë s’mund të jetë i zbrazët","Space name cannot contain the following characters: / \\\\ . : ? * \" > < |'":"Emri i hapësirës s’mund të përmbajë shenjat vijuese: / \\\\ . : ? * \" > < |'","Space name cannot exceed 255 characters":"Emri i hapësirës s’mund të tejkalojë 255 shenja","Space name was changed successfully":"Emri i hapësirë u ndryshua me sukses","Space quota was changed successfully":["Kuotat e hapësirës u ndryshuan me sukses","Kuotat për %{count} hapësira u ndryshuan me sukses"],"Space subtitle":"Nëntitull hapësire","Space subtitle was changed successfully":"Nëntitulli i hapësirës u ndryshua me sukses","Subtitle":"Nëntitull","Switch to condensed table view":"Kalo te pamje e ngjeshur e tabelës","Switch to default table view":"Kalo te pamje parazgjedhje e tabelës","Switch to tiles view":"Kalo te pamja me kuadrate","This space has %{memberShareCount} member.":["Kjo hapësirë ka %{memberShareCount} anëtar.","Kjo hapësira ka %{memberShareCount} anëtarë."],"This space has %{memberShareCount} members and %{linkShareCount} links.":"Kjo hapësirë ka %{memberShareCount} anëtarë dhe %{linkShareCount} lidhje.","This space has %{memberShareCount} members and one link.":"Kjo hapësirë ka %{memberShareCount} anëtarë dhe një lidhje.","This space has one member and %{linkShareCount} link.":["Kjo hapësirë ka një anëtar dhe %{linkShareCount} lidhje.","Kjo hapësirë ka një anëtar dhe %{linkShareCount} lidhje."],"Tile size":"Madhësi kuadrati","Total quota:":"Kuota gjithsej:","Unsaved changes":"Ndryshime të paruajtura","Used quota:":"Kuota të përdorura:","User quota was changed successfully":["Kuotat e përdoruesit u ndryshuan me sukses","Kuotat për %{count} përdorues u ndryshuan me sukses"]},"tr":{"(Opens in new window)":"(Yeni pencerede açılır)","%{ itemCount } space selected":["%{ itemCount } alan seçildi","%{ itemCount } alan seçildi"],"%{displayName} (me)":"%{displayName} (ben)","%{linkShareCount} link giving access.":["%{linkShareCount} bağlantı erişim sağlıyor.","%{linkShareCount} bağlantı erişim sağlıyor."],"%{spaceCount} space was deleted successfully":["%{spaceCount} alan başarıyla silindi","%{spaceCount} alan başarıyla silindi"],"%{spaceCount} space was disabled successfully":["%{spaceCount} alan başarıyla devre dışı bırakıldı","%{spaceCount} alan başarıyla devre dışı bırakıldı"],"%{spaceCount} space was enabled successfully":["%{spaceCount} alan başarıyla aktifleştirildi","%{spaceCount} alan başarıyla aktifleştirildi"],"%{used} of %{total} used (%{percentage}% used)":"%{used} / %{total} kullanıldı (%{percentage}% kullanıldı)","%{used} used (no restriction)":"%{used} kullanıldı (kısıtlama yok)","All Files":"Tüm Dosyalar","Are you sure you want to delete the selected space?":["Seçilen alanı silmek istediğinizden emin misiniz?","Seçilen %{count} alanı silmek istediğinizden emin misiniz?"],"Back to %{panel} panel":"%{panel} paneline geri dön","Cancel":"İptal","Change quota":"Kotayı değiştir","Change subtitle for space":"Alan için alt başlığı değiştir","Changes saved":"Değişiklikler kaydedildi","Close":"Kapat","Close file sidebar":"Yan çubuğu kapat","Confirm":"Onayla","Current Folder":"Geçerli Klasör","Delete":"Sil","Details":"Detaylar","Disable":"Devre dışı bırak","Display customization options of the files list":"Dosya listesinin özelleştirme seçeneklerini görüntüleyin","Download failed":"İndirme başarısız","Edit description":"Açıklamayı düzenle","Edit description for space %{name}":"%{name} alanı için açıklamayı düzenle","Edit quota":"Kotayı düzenle","Edit subtitle":"Alt başlığı düzenle","Enable":"Etkinleştir","Failed to change space subtitle":"Alan alt başlığı değiştirilemedi","Failed to edit space description":"Alan açıklaması düzenlenemedi","Failed to rename space":"Alan yeniden adlandırılamadı","Failed to set space description":"Alan açıklaması ayarlanamadı","File could not be located":"Dosya bulunamadı","Items per page":"Sayfa başına öge","Last activity":"Son aktivite","Manager":"Yönetici","Members":"Üyeler","No changes":"Değişiklik yok","No restriction":"Kısıtlama yok","Open link list in share panel":"Bağlantı listesini paylaşım panelinde aç","Open member list in share panel":"Üye listesini paylaşım panelinde aç","Open share panel":"Paylaşım panelini aç","Overview of the information about the selected space":"Seçilen alan hakkındaki bilgilere genel bakış","Please enter only numbers":"Lütfen sadece sayı girin","Quota":"Kota","Quota was changed successfully":"Kota başarıyla değiştirildi","Rename":"Yeniden adlandır","Rename space":"Alanı yeniden adlandır","Revert":"Geri al","Save":"Kaydet","Set as space description":"Alan açıklaması olarak ayarla","Show":"Göster","Show context menu":"İçerik menüsünü göster","Show file extensions":"Dosya uzantılarını göster","Show hidden files":"Gizli dosyaları göster","Sort by %{ name }":"%{ name } 'e göre sırala","Space description":"Alan açıklaması","Space description was edited successfully":"Alan açıklaması başarıyla düzenlendi","Space description was set successfully":"Alan açıklaması başarıyla ayarlandı","Space name":"Alan adı","Space name cannot be empty":"Alan adı boş olamaz","Space name was changed successfully":"Alan adı başarıyla değiştirildi","Space subtitle":"Alan alt başlığı","Space subtitle was changed successfully":"Alan alt başlığı başarıyla değiştirildi","Subtitle":"Alt başlık","This space has %{memberShareCount} member.":["Bu alanda %{memberShareCount} üye var.","Bu alanda %{memberShareCount} üye var."],"This space has one member and %{linkShareCount} link.":["Bu alanın bir üyesi ve %{linkShareCount} bağlantısı var.","Bu alanın bir üyesi ve %{linkShareCount} bağlantısı var."],"Total quota:":"Toplam kota:","Unsaved changes":"Kaydedilmeyen değişiklikler","Used quota:":"Kullanılan kota:"}} \ No newline at end of file diff --git a/packages/web-pkg/package.json b/packages/web-pkg/package.json index 951aac198fa..dae274c7604 100644 --- a/packages/web-pkg/package.json +++ b/packages/web-pkg/package.json @@ -14,14 +14,16 @@ "directory": "packages/web-pkg" }, "peerDependencies": { - "@casl/vue": "^2.2.1", "@casl/ability": "^6.3.3", + "@casl/vue": "^2.2.1", "axios": "^0.27.2 || ^1.0.0", + "design-system": "workspace:@ownclouders/design-system@*", "filesize": "^9.0.11", "fuse.js": "^6.5.3", "lodash-es": "^4.17.21", "luxon": "^2.4.0", "mark.js": "^8.11.1", + "pinia": "^2.1.3", "qs": "^6.10.3", "semver": "^7.3.8", "uuid": "^9.0.0", diff --git a/packages/web-pkg/src/apps/types.ts b/packages/web-pkg/src/apps/types.ts index be19c454b02..72c32ac8294 100644 --- a/packages/web-pkg/src/apps/types.ts +++ b/packages/web-pkg/src/apps/types.ts @@ -1,6 +1,7 @@ -import { App, ComponentCustomProperties } from 'vue' -import { RouteLocationRaw, Router } from 'vue-router' +import { App, ComponentCustomProperties, Ref } from 'vue' +import { RouteLocationRaw, Router, RouteRecordRaw } from 'vue-router' import { Store } from 'vuex' +import { Extension } from 'web-pkg/src/composables/piniaStores' export interface AppReadyHookArgs { announceExtension: (extension: { [key: string]: unknown }) => void @@ -20,3 +21,72 @@ export interface AppNavigationItem { route?: RouteLocationRaw tag?: string } + +/** + * ApplicationQuickAction describes an application action that is used in the runtime. + * + * @deprecated In the future quick actions should be registered just like any other extension. Fine + * to use this interface for now, but it will be changed in the near future. + */ +export interface ApplicationQuickAction { + id?: string + label?: string + icon?: string + handler?: () => Promise + displayed?: boolean +} + +/** + * ApplicationQuickActions describes a map of application actions that are used in the runtime + * + * @deprecated In the future quick actions should be registered just like any other extension. Fine + * to use this interface for now, but it will be changed in the near future. + */ +export interface ApplicationQuickActions { + [key: string]: ApplicationQuickAction +} + +export type AppConfigObject = Record + +/** ApplicationInformation describes required information of an application */ +export interface ApplicationInformation { + id?: string + name?: string + icon?: string + isFileEditor?: boolean + extensions?: any[] + fileSideBars?: any[] +} + +/** + * ApplicationTranslations is a map of language keys to translations + */ +export interface ApplicationTranslations { + [lang: string]: { + [key: string]: string + } +} + +/** ClassicApplicationScript reflects classic application script structure */ +export interface ClassicApplicationScript { + appInfo?: ApplicationInformation + store?: Store + routes?: ((...args) => RouteRecordRaw[]) | RouteRecordRaw[] + navItems?: ((...args) => AppNavigationItem[]) | AppNavigationItem[] + quickActions?: ApplicationQuickActions + translations?: ApplicationTranslations + extensions?: Ref + initialize?: () => void + ready?: () => void + mounted?: () => void + // TODO: move this to its own type + setup?: (args: { applicationConfig: AppConfigObject }) => ClassicApplicationScript +} + +export type ApplicationSetupOptions = { applicationConfig: AppConfigObject } + +export const defineWebApplication = (args: { + setup: (options: ApplicationSetupOptions) => ClassicApplicationScript +}) => { + return args +} diff --git a/packages/web-pkg/src/cern/components/CollapsibleOcTable.vue b/packages/web-pkg/src/cern/components/CollapsibleOcTable.vue new file mode 100644 index 00000000000..2daaede5eb7 --- /dev/null +++ b/packages/web-pkg/src/cern/components/CollapsibleOcTable.vue @@ -0,0 +1,765 @@ + + + diff --git a/packages/web-pkg/src/cern/composables/index.ts b/packages/web-pkg/src/cern/composables/index.ts new file mode 100644 index 00000000000..35b03f0f88e --- /dev/null +++ b/packages/web-pkg/src/cern/composables/index.ts @@ -0,0 +1,2 @@ +export * from './useGroupingSettings' +export * from './useLoadTokenInfo' diff --git a/packages/web-pkg/src/cern/composables/useGroupingSettings.ts b/packages/web-pkg/src/cern/composables/useGroupingSettings.ts new file mode 100644 index 00000000000..99b780293f3 --- /dev/null +++ b/packages/web-pkg/src/cern/composables/useGroupingSettings.ts @@ -0,0 +1,104 @@ +import { computed, Ref, unref } from 'vue' + +export const useGroupingSettings = ({ + sortBy, + sortDir +}: { + sortBy: Ref + sortDir: Ref +}) => { + const groupingSettings = computed(() => { + return { + groupingBy: localStorage.getItem('grouping-shared-with-me') || 'Shared on', + showGroupingOptions: true, + groupingFunctions: { + 'Name alphabetically': function (row) { + localStorage.setItem('grouping-shared-with-me', 'Name alphabetically') + if (!isNaN(row.name.charAt(0))) { + return '#' + } + if (row.name.charAt(0) === '.') { + return row.name.charAt(1).toLowerCase() + } + return row.name.charAt(0).toLowerCase() + }, + 'Shared on': function (row) { + localStorage.setItem('grouping-shared-with-me', 'Shared on') + const recently = Date.now() - 604800000 + const lastMonth = Date.now() - 2592000000 + if (Date.parse(row.sdate) < lastMonth) { + return 'Older' + } + if (Date.parse(row.sdate) >= recently) { + return 'Recently' + } else { + return 'Last month' + } + }, + 'Share owner': function (row) { + localStorage.setItem('grouping-shared-with-me', 'Share owner') + return row.share?.owner?.displayName + }, + None: function () { + localStorage.setItem('grouping-shared-with-me', 'None') + } + }, + sortGroups: { + 'Name alphabetically': function (groups) { + // sort in alphabetical order by group name + const sortedGroups = groups.sort(function (a, b) { + if (a.name < b.name) { + return -1 + } + if (a.name > b.name) { + return 1 + } + return 0 + }) + // if sorting is done by name, reverse groups depending on asc/desc + if (unref(sortBy) === 'name' && unref(sortDir) === 'desc') { + sortedGroups.reverse() + } + return sortedGroups + }, + 'Shared on': function (groups) { + // sort in order: 1-Recently, 2-Last month, 3-Older + const sortedGroups = [] + const options = ['Recently', 'Last month', 'Older'] + for (const o of options) { + const found = groups.find((el) => el.name.toLowerCase() === o.toLowerCase()) + if (found) { + sortedGroups.push(found) + } + } + // if sorting is done by sdate, reverse groups depending on asc/desc + if (unref(sortBy) === 'sdate' && unref(sortDir) === 'asc') { + sortedGroups.reverse() + } + return sortedGroups + }, + 'Share owner': function (groups) { + // sort in alphabetical order by group name + const sortedGroups = groups.sort(function (a, b) { + if (a.name < b.name) { + return -1 + } + if (a.name > b.name) { + return 1 + } + return 0 + }) + // if sorting is done by owner, reverse groups depending on asc/desc + if (unref(sortBy) === 'owner' && unref(sortDir) === 'desc') { + sortedGroups.reverse() + } + return sortedGroups + } + } + } + }) + + return { + groupingSettings + } +} diff --git a/packages/web-pkg/src/cern/composables/useLoadTokenInfo.ts b/packages/web-pkg/src/cern/composables/useLoadTokenInfo.ts new file mode 100644 index 00000000000..9260f459171 --- /dev/null +++ b/packages/web-pkg/src/cern/composables/useLoadTokenInfo.ts @@ -0,0 +1,9 @@ +import { useTask } from 'vue-concurrency' + +export function useLoadTokenInfo() { + const loadTokenInfoTask = useTask(function* () { + return {} + }) + + return { loadTokenInfoTask } +} diff --git a/packages/web-pkg/src/components/AppTopBar.vue b/packages/web-pkg/src/components/AppTopBar.vue index 6366e5cc3dc..4ac41b573f4 100644 --- a/packages/web-pkg/src/components/AppTopBar.vue +++ b/packages/web-pkg/src/components/AppTopBar.vue @@ -1,45 +1,164 @@ - diff --git a/packages/web-pkg/src/components/BatchActions.vue b/packages/web-pkg/src/components/BatchActions.vue index 1c428d2e077..a70d149d108 100644 --- a/packages/web-pkg/src/components/BatchActions.vue +++ b/packages/web-pkg/src/components/BatchActions.vue @@ -9,7 +9,7 @@ :key="`action-${index}`" :action="action" :action-options="actionOptions" - appearance="outline" + appearance="raw" class="batch-actions oc-mr-s" :shortcut-hint="false" :show-tooltip="limitedScreenSpace" @@ -46,6 +46,17 @@ export default defineComponent({ diff --git a/packages/web-pkg/src/components/Spaces/QuotaModal.vue b/packages/web-pkg/src/components/Spaces/QuotaModal.vue index 45f3906931b..cb6753e3fdc 100644 --- a/packages/web-pkg/src/components/Spaces/QuotaModal.vue +++ b/packages/web-pkg/src/components/Spaces/QuotaModal.vue @@ -35,7 +35,7 @@ import { useGettext } from 'vue3-gettext' import QuotaSelect from 'web-pkg/src/components/QuotaSelect.vue' import { SpaceResource } from 'web-client/src' import { eventBus, useClientService, useRouter } from 'web-pkg/src' -import { useStore } from 'web-pkg/src/composables' +import { useStore, useLoadingService } from 'web-pkg/src/composables' import { Drive } from 'web-client/src/generated' export default defineComponent({ @@ -77,6 +77,7 @@ export default defineComponent({ const store = useStore() const { $gettext, $ngettext } = useGettext() const clientService = useClientService() + const loadingService = useLoadingService() const router = useRouter() const selectedOption = ref(0) @@ -181,7 +182,9 @@ export default defineComponent({ value: driveData.quota }) }) - const results = await Promise.allSettled>(requests) + const results = await loadingService.addTask(() => { + return Promise.allSettled>(requests) + }) const succeeded = results.filter((r) => r.status === 'fulfilled') if (succeeded.length) { store.dispatch('showMessage', { title: getSuccessMessage(succeeded.length) }) @@ -189,7 +192,10 @@ export default defineComponent({ const errors = results.filter((r) => r.status === 'rejected') if (errors.length) { errors.forEach(console.error) - store.dispatch('showMessage', { title: getErrorMessage(errors.length) }) + store.dispatch('showErrorMessage', { + title: getErrorMessage(errors.length), + errors: (errors as PromiseRejectedResult[]).map((f) => f.reason) + }) } props.cancel() diff --git a/packages/web-pkg/src/components/Spaces/ReadmeContentModal.vue b/packages/web-pkg/src/components/Spaces/ReadmeContentModal.vue index 1d72028a9f5..1abfd2a0a88 100644 --- a/packages/web-pkg/src/components/Spaces/ReadmeContentModal.vue +++ b/packages/web-pkg/src/components/Spaces/ReadmeContentModal.vue @@ -55,7 +55,7 @@ export default defineComponent({ this.readmeContent = await this.$client.files.getFileContents(decodeURI(path)) }, methods: { - ...mapActions(['showMessage']), + ...mapActions(['showMessage', 'showErrorMessage']), ...mapMutations('Files', ['UPDATE_RESOURCE_FIELD']), editReadme() { @@ -77,9 +77,9 @@ export default defineComponent({ }) .catch((error) => { console.error(error) - this.showMessage({ + this.showErrorMessage({ title: this.$gettext('Failed to edit space description'), - status: 'danger' + error }) }) } diff --git a/packages/web-pkg/src/components/ViewOptions.vue b/packages/web-pkg/src/components/ViewOptions.vue index 8e020965bb9..96eeb73bb22 100644 --- a/packages/web-pkg/src/components/ViewOptions.vue +++ b/packages/web-pkg/src/components/ViewOptions.vue @@ -73,11 +73,11 @@ >