Compare commits

...

47 Commits

Author SHA1 Message Date
Côme Chilliet 9c51ed7439 chore: Remove deprecated methods from notification manager
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2026-04-09 18:05:54 +02:00
Côme Chilliet 78fd649e47 chore: Remove long deprecated methods from OCP
These have been deprecated from before 20

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
2026-04-09 18:05:53 +02:00
Ferdinand Thiessen 1219c8e152 Merge pull request #59325 from mykh-hailo/fix/profile-ui
fix: profile page on small screens
2026-04-09 15:43:38 +02:00
Louis e3e499a63a Merge pull request #59348 from mykh-hailo/feat/ui-enhancement
feat: remove password column from user table
2026-04-09 12:45:26 +02:00
Joas Schilling 9ff0adf239 Merge pull request #59522 from nextcloud/bugfix/noid/make-maintenance-mode-more-obvious
fix(updater): Make "maintenance mode kept active" more obvious
2026-04-09 10:37:44 +02:00
Joas Schilling d6ee1eafa3 fix(updater): Make "maintenance mode kept active" more obvious
Signed-off-by: Joas Schilling <coding@schilljs.com>
2026-04-09 09:10:21 +02:00
Benjamin Gaussorgues c835064a6b Merge pull request #59494 from nextcloud/artonge/fix/drop_transaction_during_scans 2026-04-09 08:56:50 +02:00
Nextcloud bot 5b7ea4e858 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-04-09 00:19:00 +00:00
mykh-hailo d3ef95c78f fix: profile page on small screens
Signed-off-by: mykh-hailo <kristianderonta0205@gmail.com>
2026-04-08 13:13:42 -04:00
Robin Appelman e761005e52 Merge pull request #57360 from nextcloud/fix/trashbin-atomic-cache
fix(trashbin): keep cache and db consistent
2026-04-08 18:04:25 +02:00
mykh-hailo 415ecb1cf5 feat: remove password column from user table
Signed-off-by: mykh-hailo <kristianderonta0205@gmail.com>
2026-04-08 12:03:56 -04:00
Ferdinand Thiessen a30653c6cb Merge pull request #59486 from nextcloud/fix/drop-files-subfolder
fix(files): properly handle dropped files on subfolders
2026-04-08 17:43:07 +02:00
nextcloud-command 48c3933cb6 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2026-04-08 15:22:45 +00:00
Ferdinand Thiessen d02df72efe fix(files): properly handle dropped files on subfolders
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-04-08 17:03:24 +02:00
Git'Fellow 9fd6c4abc1 Merge pull request #59296 from nextcloud/automated/noid/rector-changes
Apply rector changes
2026-04-08 15:29:02 +02:00
Louis Chmn 680ddd93c2 fix(Scanner): Remove high level transaction during scans
Signed-off-by: Louis Chmn <louis@chmn.me>
2026-04-08 14:34:45 +02:00
Louis Chmn 72812b2b07 chore(Scanner): Use modern syntax and APIs
Signed-off-by: Louis Chmn <louis@chmn.me>
2026-04-08 12:26:55 +02:00
Nextcloud bot 2d91bdd74c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-04-08 00:19:10 +00:00
Ferdinand Thiessen 053f725f98 Merge pull request #59427 from nextcloud/fix/dashboard-list
fix(dashboard): remove status list if there are none
2026-04-07 17:27:32 +02:00
Ferdinand Thiessen 0b5ce4e78a Merge pull request #59423 from nextcloud/fix/webauthn-list
fix(settings): hide list of webauthn devices if empty
2026-04-07 17:27:12 +02:00
nextcloud-command 2fd8961b85 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2026-04-07 15:10:00 +00:00
nextcloud-command 9d70509221 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2026-04-07 15:05:32 +00:00
Ferdinand Thiessen 29628eba33 fix(dashboard): remove status list if there are none
- resolves https://github.com/nextcloud/server/issues/59334

If there are no status entries the list must be removed.
Its not valid to show an empty list as per accessibility rules:
https://www.w3.org/TR/wai-aria-1.2/#mustContain

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-04-07 16:56:30 +02:00
Ferdinand Thiessen b060efaca4 fix(settings): hide list of webauthn devices if empty
- resolves https://github.com/nextcloud/server/issues/59339

If there are no devices the list would be empty.
This is invalid for accessibility, as every list needs at least one
listitem.
Ref: https://www.w3.org/TR/wai-aria-1.2/#mustContain

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-04-07 16:56:21 +02:00
Ferdinand Thiessen 13583e8c6b Merge pull request #59424 from nextcloud/fix/systemtags-inline
fix(systemtags): only render inline list of tags if there are some
2026-04-07 16:55:25 +02:00
Ferdinand Thiessen 0526c18ac2 Merge pull request #59430 from nextcloud/chore/logger
refactor(files): use consistent logger import
2026-04-07 16:55:11 +02:00
Louis 239c8ccfe1 Merge pull request #59463 from nextcloud/automated/noid/update-min-supported-desktop-version
chore: Update minimum supported desktop version to 3.1.83
2026-04-07 15:58:29 +02:00
nextcloud-command b9e4a2a115 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2026-04-07 13:47:58 +00:00
Benjamin Gaussorgues 1ed4f74551 Merge pull request #58892 from nextcloud/chore/add-test-snowflake-32 2026-04-07 15:47:50 +02:00
nextcloud-command 66881a7cc9 chore(assets): Recompile assets
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2026-04-07 13:39:54 +00:00
Ferdinand Thiessen a58a2961f3 fix(systemtags): only render inline list of tags if there are some
- resolves #59332

If there are no tags available, then we cannot render an `<ul>` element
as this would result in invalid HTML / invalid accessibility state.
Ref: https://www.w3.org/TR/wai-aria-1.2/#mustContain

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-04-07 14:45:44 +02:00
Ferdinand Thiessen 044f4f076e refactor(files): use consistent logger import
We try to use named exports everywhere so also for the logger for
consistency. Also the logger is more of a util then a main entry point
so moved the implementation to the utils directory.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
2026-04-07 14:41:03 +02:00
Côme Chilliet 3f6d0c4192 Merge pull request #59462 from Keeper-of-the-Keys/config-header-attempt-2
feat: add config header and tests.
2026-04-07 14:40:33 +02:00
E.S. Rosenberg a.k.a. Keeper of the Keys 3f539d78e3 fix: change config-warning to const per @come-nc request in the PR.
Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es-github@rosenberg.org.il>
2026-04-07 11:47:14 +03:00
nextcloud-command c5e3677d28 chore: Update minimum supported desktop version
Signed-off-by: GitHub <noreply@github.com>
2026-04-06 00:47:07 +00:00
E.S. Rosenberg a.k.a. Keeper of the Keys 52d092da64 feat: add config header and tests.
Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es-github@rosenberg.org.il>
2026-04-06 00:46:26 +03:00
nextcloud-command bf729c53d7 refactor: Apply rector changes
Signed-off-by: GitHub <noreply@github.com>
2026-04-05 14:47:27 +00:00
Benjamin Gaussorgues 1b241388ee chore(tests): run 32 bits tests when PreviewMapper test change
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
2026-04-03 17:01:47 +02:00
Benjamin Gaussorgues 71aa36860f chore(tests): check Snowflake ID preserved in 32 bits
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
2026-04-03 17:00:01 +02:00
Robin Appelman 0317e002f3 test: skip testTrashEntryCreatedWhenSourceNotInCache on object store
Signed-off-by: Robin Appelman <robin@icewind.nl>
2026-04-01 20:23:42 +02:00
Robin Appelman 966db54089 chore: psalm fix
Signed-off-by: Robin Appelman <robin@icewind.nl>
2026-04-01 19:25:27 +02:00
Robin Appelman b76cdd09cc fix: catch all exceptions during trashbin cache move
Signed-off-by: Robin Appelman <robin@icewind.nl>
2026-04-01 19:24:26 +02:00
Robin Appelman 921ee17026 test: add test for trashbin when cross-storage move fails
Signed-off-by: Robin Appelman <robin@icewind.nl>
2026-04-01 19:24:25 +02:00
Robin Appelman 7eae0e5f8c chore: deduplicate trashbin row delete logic
Signed-off-by: Robin Appelman <robin@icewind.nl>
2026-04-01 18:09:29 +02:00
Hoang Pham b7150ad88c perf(trashbin): avoid full rescan for uncached moves
Signed-off-by: Hoang Pham <hoangmaths96@gmail.com>
2026-04-01 18:00:21 +02:00
Hoang Pham eedd8dce3c fix(trashbin): keep metadata consistent on move
Signed-off-by: Hoang Pham <hoangmaths96@gmail.com>
2026-04-01 18:00:21 +02:00
Hoang Pham ac9c17c7b8 fix: keep trashbin cache and db in sync
Signed-off-by: Hoang Pham <hoangmaths96@gmail.com>
2026-04-01 18:00:19 +02:00
175 changed files with 1710 additions and 1564 deletions
+1
View File
@@ -9,6 +9,7 @@ on:
- ".github/workflows/phpunit-32bits.yml"
- "tests/phpunit-autotest.xml"
- "lib/private/Snowflake/*"
- "tests/lib/Preview/PreviewMapperTest.php"
workflow_dispatch:
schedule:
- cron: "15 1 * * 1-6"
+32 -27
View File
@@ -1,32 +1,37 @@
OC.L10N.register(
"comments",
{
"Comments" : "Comments",
"You commented" : "You commented",
"{author} commented" : "{author} commented",
"You commented on %1$s" : "You commented on %1$s",
"You commented on {file}" : "You commented on {file}",
"%1$s commented on %2$s" : "%1$s commented on %2$s",
"{author} commented on {file}" : "{author} commented on {file}",
"<strong>Comments</strong> for files" : "<strong>Comments</strong> for files",
"{user} mentioned you in a comment on \"{file}\"" : "{user} mentioned you in a comment on \"{file}\"",
"Files app plugin to add comments to files" : "Files app plugin to add comments to files",
"Edit comment" : "Edit comment",
"Delete comment" : "Delete comment",
"Cancel edit" : "Cancel edit",
"Post comment" : "Post comment",
"@ for mentions, : for emoji, / for smart picker" : "@ for mentions, : for emoji, / for smart picker",
"Could not reload comments" : "Could not reload comments",
"Failed to mark comments as read" : "Failed to mark comments as read",
"Unable to load the comments list" : "Unable to load the comments list",
"No comments yet, start the conversation!" : "No comments yet, start the conversation!",
"No more messages" : "No more messages",
"Retry" : "Retry",
"_1 new comment_::_{unread} new comments_" : ["1 new comment","{unread} new comments"],
"Comment" : "Comment",
"An error occurred while trying to edit the comment" : "An error occurred while trying to edit the comment",
"Comment deleted" : "Comment deleted",
"An error occurred while trying to delete the comment" : "An error occurred while trying to delete the comment",
"An error occurred while trying to create the comment" : "An error occurred while trying to create the comment"
"Comments" : "კომენტარები",
"You commented" : "თქვენი კომენტარი",
"{author} commented" : "{author}-ის კომენტარი",
"You commented on %1$s" : "თქვენი კომენტარი %1$s-ზე",
"You commented on {file}" : "თქვენი კომენტარი {file}-ზე",
"%1$s commented on %2$s" : "%1$s-მა დატოვა კომენტარი %2$s-ზე",
"{author} commented on {file}" : "{author}-მა დატოვა კომენტარი {file}-ზე",
"<strong>Comments</strong> for files" : "<strong>კომენტარები</strong> ფაილებისთვის",
"Files" : "ფაილები",
"You were mentioned on \"{file}\", in a comment by an account that has since been deleted" : "თქვენ გახსენეს \"{file}\"-ის კომენტარში ანგარიშიდან, რომელიც მის შემდეგ წაიშალა",
"{user} mentioned you in a comment on \"{file}\"" : "{user}-მა გახსენათ კომენტარში ფაილზე \"{file}\"",
"Files app plugin to add comments to files" : "Files აპის დამატება ფაილებზე კომენტარების დასამატებლად",
"Edit comment" : "კომენტარის ჩასწორება",
"Delete comment" : "კომენტარის წაშლა",
"Cancel edit" : "ჩასწორების გაუქმება",
"New comment" : "ახალი კომენტარი",
"Write a comment …" : "კომენტარის დაწერა …",
"Post comment" : "კომენტარის დაპოსტვა",
"@ for mentions, : for emoji, / for smart picker" : "@ ხსენებისთვის, : ემოჯისთვის, / ჭკვიანი ამრჩევისთვის",
"Could not reload comments" : "კომენტარების თავიდან ჩატვირთვა შეუძლებელია",
"Failed to mark comments as read" : "კომენტარების წაკითხულად მონიშვნა ჩავარდა",
"Unable to load the comments list" : "კომენტარების სიის ჩატვირთვა ჩავარდა",
"No comments yet, start the conversation!" : "ჯერ კომენტარები არაა. დაიწყეთ საუბარი!",
"No more messages" : "მეტი შეტყობინება აღარაა",
"Retry" : "თავიდან ცდა",
"_1 new comment_::_{unread} new comments_" : ["1 ახალი კომენტარი","{unread} ახალი კომენტარი"],
"Comment" : "კომენტარი",
"An error occurred while trying to edit the comment" : "კომენტარის ჩასწორებისას აღმოჩენილია შეცდომა",
"Comment deleted" : "კომენტარი წაიშალა",
"An error occurred while trying to delete the comment" : "კომენტარის წაშლისას აღმოჩენილია შეცდომა",
"An error occurred while trying to create the comment" : "კომენტარის შექმნისას აღმოჩენილია შეცდომა",
"Write a comment …" : "კომენტარის დაწერა …"
},
"nplurals=2; plural=(n!=1);");
+32 -27
View File
@@ -1,30 +1,35 @@
{ "translations": {
"Comments" : "Comments",
"You commented" : "You commented",
"{author} commented" : "{author} commented",
"You commented on %1$s" : "You commented on %1$s",
"You commented on {file}" : "You commented on {file}",
"%1$s commented on %2$s" : "%1$s commented on %2$s",
"{author} commented on {file}" : "{author} commented on {file}",
"<strong>Comments</strong> for files" : "<strong>Comments</strong> for files",
"{user} mentioned you in a comment on \"{file}\"" : "{user} mentioned you in a comment on \"{file}\"",
"Files app plugin to add comments to files" : "Files app plugin to add comments to files",
"Edit comment" : "Edit comment",
"Delete comment" : "Delete comment",
"Cancel edit" : "Cancel edit",
"Post comment" : "Post comment",
"@ for mentions, : for emoji, / for smart picker" : "@ for mentions, : for emoji, / for smart picker",
"Could not reload comments" : "Could not reload comments",
"Failed to mark comments as read" : "Failed to mark comments as read",
"Unable to load the comments list" : "Unable to load the comments list",
"No comments yet, start the conversation!" : "No comments yet, start the conversation!",
"No more messages" : "No more messages",
"Retry" : "Retry",
"_1 new comment_::_{unread} new comments_" : ["1 new comment","{unread} new comments"],
"Comment" : "Comment",
"An error occurred while trying to edit the comment" : "An error occurred while trying to edit the comment",
"Comment deleted" : "Comment deleted",
"An error occurred while trying to delete the comment" : "An error occurred while trying to delete the comment",
"An error occurred while trying to create the comment" : "An error occurred while trying to create the comment"
"Comments" : "კომენტარები",
"You commented" : "თქვენი კომენტარი",
"{author} commented" : "{author}-ის კომენტარი",
"You commented on %1$s" : "თქვენი კომენტარი %1$s-ზე",
"You commented on {file}" : "თქვენი კომენტარი {file}-ზე",
"%1$s commented on %2$s" : "%1$s-მა დატოვა კომენტარი %2$s-ზე",
"{author} commented on {file}" : "{author}-მა დატოვა კომენტარი {file}-ზე",
"<strong>Comments</strong> for files" : "<strong>კომენტარები</strong> ფაილებისთვის",
"Files" : "ფაილები",
"You were mentioned on \"{file}\", in a comment by an account that has since been deleted" : "თქვენ გახსენეს \"{file}\"-ის კომენტარში ანგარიშიდან, რომელიც მის შემდეგ წაიშალა",
"{user} mentioned you in a comment on \"{file}\"" : "{user}-მა გახსენათ კომენტარში ფაილზე \"{file}\"",
"Files app plugin to add comments to files" : "Files აპის დამატება ფაილებზე კომენტარების დასამატებლად",
"Edit comment" : "კომენტარის ჩასწორება",
"Delete comment" : "კომენტარის წაშლა",
"Cancel edit" : "ჩასწორების გაუქმება",
"New comment" : "ახალი კომენტარი",
"Write a comment …" : "კომენტარის დაწერა …",
"Post comment" : "კომენტარის დაპოსტვა",
"@ for mentions, : for emoji, / for smart picker" : "@ ხსენებისთვის, : ემოჯისთვის, / ჭკვიანი ამრჩევისთვის",
"Could not reload comments" : "კომენტარების თავიდან ჩატვირთვა შეუძლებელია",
"Failed to mark comments as read" : "კომენტარების წაკითხულად მონიშვნა ჩავარდა",
"Unable to load the comments list" : "კომენტარების სიის ჩატვირთვა ჩავარდა",
"No comments yet, start the conversation!" : "ჯერ კომენტარები არაა. დაიწყეთ საუბარი!",
"No more messages" : "მეტი შეტყობინება აღარაა",
"Retry" : "თავიდან ცდა",
"_1 new comment_::_{unread} new comments_" : ["1 ახალი კომენტარი","{unread} ახალი კომენტარი"],
"Comment" : "კომენტარი",
"An error occurred while trying to edit the comment" : "კომენტარის ჩასწორებისას აღმოჩენილია შეცდომა",
"Comment deleted" : "კომენტარი წაიშალა",
"An error occurred while trying to delete the comment" : "კომენტარის წაშლისას აღმოჩენილია შეცდომა",
"An error occurred while trying to create the comment" : "კომენტარის შექმნისას აღმოჩენილია შეცდომა",
"Write a comment …" : "კომენტარის დაწერა …"
},"pluralForm" :"nplurals=2; plural=(n!=1);"
}
+1 -1
View File
@@ -5,7 +5,7 @@
<template>
<main id="app-dashboard">
<h2>{{ greeting.text }}</h2>
<ul class="statuses">
<ul v-if="sortedRegisteredStatus.length > 0" class="statuses">
<li
v-for="status in sortedRegisteredStatus"
:id="'status-' + status"
@@ -49,7 +49,7 @@ class BlockLegacyClientPlugin extends ServerPlugin {
return;
}
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.1.81');
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '3.1.83');
$maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99');
// Check if the client is a desktop client
+21
View File
@@ -79,6 +79,7 @@ OC.L10N.register(
"Go to the \"{dir}\" directory" : "Fara í heimamöppu",
"Current directory path" : "Fyrirliggjandi slóð að möppu",
"Share" : "Deila",
"Reload content" : "Endurlesa efni",
"Your have used your space quota and cannot upload files anymore" : "Þú hefur fullnýtt geymslukvótann þinn og getur ekki lengur sent inn skrár",
"You do not have permission to upload or create files here." : "Þú hefur ekki heimild til að senda inn eða búa til skrár hér.",
"Drag and drop files here to upload" : "Dragðu og slepptu hér skrám til að senda inn",
@@ -94,6 +95,7 @@ OC.L10N.register(
"Another entry with the same name already exists." : "Önnur færsla með sama heiti er þegar til staðar.",
"Invalid filename." : "Ógilt skráarheiti.",
"Rename file" : "Endurnefna skrá",
"Recently created" : "Nýlega útbúið",
"Folder" : "Mappa",
"Unknown file type" : "Óþekkt skráartegund",
"{ext} image" : "{ext} mynd",
@@ -109,6 +111,7 @@ OC.L10N.register(
"Last 30 days" : "Síðustu 30 daga",
"This year ({year})" : "Þetta ár ({year})",
"Last year ({year})" : "Síðasta ár ({year})",
"Custom range" : "Sérsniðið bil",
"Custom date range" : "Sérsniðið dagsetningabil",
"Search everywhere" : "Leita allsstaðar",
"Documents" : "Skjöl",
@@ -120,6 +123,7 @@ OC.L10N.register(
"Images" : "Myndir",
"Videos" : "Myndskeið",
"Filters" : "Síur",
"Back to filters" : "Til baka í síur",
"Appearance" : "Útlit",
"Show hidden files" : "Sýna faldar skrár",
"Show file type column" : "Sýna dálk fyrir skráategund",
@@ -128,6 +132,7 @@ OC.L10N.register(
"General" : "Almennt",
"Sort favorites first" : "Raða eftirlætum fremst",
"Sort folders before files" : "Raða möppum á undan skrám",
"Enable folder tree view" : "Virkja möppugreinasýn",
"Default view" : "Sjálfgefin sýn",
"All files" : "Allar skrár",
"Personal files" : "Einkaskrár",
@@ -150,7 +155,10 @@ OC.L10N.register(
"Show those shortcuts" : "Sýna þessa flýtilykla",
"Warnings" : "Aðvaranir",
"Warn before changing a file extension" : "Aðvara áður en skráarendingu er breytt",
"Warn before deleting a file" : "Aðvara áður en skrá er eytt",
"WebDAV URL" : "WebDAV-slóð",
"Create an app password" : "Búa til lykilorð forrits",
"Required for WebDAV authentication because Two-Factor Authentication is enabled for this account." : "Er krafist fyrir WebDAV-auðkenningu þar sem tveggja-þátta auðkenning er virkjuð fyrir þennan aðgang.",
"How to access files using WebDAV" : "Hvernig á að nálgast skrár í gegnum WebDAV",
"Total rows summary" : "Samantek á fjölda raða",
"Toggle selection for all files and folders" : "Víxla vali af/á fyrir allar skrár og möppur",
@@ -227,6 +235,9 @@ OC.L10N.register(
"Removing the file extension \"{old}\" may render the file unreadable." : "Sé skráaendingin \"{old}\" fjarlægð, gæti skráin orðið ólæsileg.",
"Adding the file extension \"{new}\" may render the file unreadable." : "Sé skráaendingunni \"{new}\" bætt við, gæti skráin orðið ólæsileg.",
"Do not show this dialog again." : "Ekki sýna þennan glugga aftur.",
"Rename file to hidden" : "Endurnefna skrá sem falda",
"Prefixing a filename with a dot may render the file hidden." : "Sé settur punktur framan við skráaheiti, gæti skráin orðið falin.",
"Are you sure you want to rename the file to \"{filename}\"?" : "Ertu viss um að þú viljir endurnefna skrána sem \"{filename}\"?",
"Cancel" : "Hætta við",
"Rename" : "Endurnefna",
"Select file or folder to link to" : "Veldu skrá eða möppu til að tengja í",
@@ -241,11 +252,13 @@ OC.L10N.register(
"Error during upload: {message}" : "Villa við innsendingu: {message}",
"Error during upload, status code {status}" : "Villa við innsendingu, stöðukóði: {status}",
"Unknown error during upload" : "Óþekkt villa við innsendingu",
"File list is reloading" : "Skráalisti er að hlaðast inn",
"Loading current folder" : "Hleð inn núverandi möppu",
"Retry" : "Reyna aftur",
"No files in here" : "Engar skrár hér",
"Upload some content or sync with your devices!" : "Sendu inn eitthvað efni eða samstilltu við tækin þín!",
"Go back" : "Fara til baka",
"Loading …" : "Hleð inn…",
"Your files" : "Skrárnar þínar",
"Open in files" : "Opna í skráaforritinu",
"File cannot be accessed" : "Skráin er ekki aðgengileg",
@@ -269,6 +282,9 @@ OC.L10N.register(
"Failed to convert files: {message}" : "Mistókst að umbreyta skrám: {message}",
"All files failed to be converted" : "Mistókst að umbreyta öllum skrám",
"One file could not be converted: {message}" : "Ekki var hægt að umbreyta einni skrá: {message}",
"_%n file could not be converted_::_%n files could not be converted_" : ["Ekki var hægt að umbreyta %n skrá","Ekki var hægt að umbreyta %n skrám"],
"_%n file converted_::_%n files converted_" : ["%n skrá umbreytt","%n skrám umbreytt"],
"Files converted" : "Skrám umbreytt",
"Failed to convert files" : "Mistókst að umbreyta skrám",
"Converting file …" : "Umbreyti skrá…",
"File successfully converted" : "Tókst að umbreyta skrá",
@@ -304,7 +320,9 @@ OC.L10N.register(
"The files are locked" : "Skrárnar eru læstar",
"The file does not exist anymore" : "Skráin er ekki lengur til",
"Moving \"{source}\" to \"{destination}\" …" : "Færi \"{source}\" í \"{destination}\" …",
"Moving {count} files to \"{destination}\" …" : "Færi {count} skrár í \"{destination}\" …",
"Copying \"{source}\" to \"{destination}\" …" : "Afrita \"{source}\" í \"{destination}\" …",
"Copying {count} files to \"{destination}\" …" : "Afrita {count} skrár í \"{destination}\" …",
"Choose destination" : "Veldu áfangastað",
"Copy to {target}" : "Afrita í {target}",
"Move to {target}" : "Færa í {target}",
@@ -319,6 +337,8 @@ OC.L10N.register(
"Retry and close" : "Prófa aftur og loka",
"Open online" : "Opna á netinu",
"Details" : "Nánar",
"Open the details sidebar" : "Opna hliðarspjald með ítarupplýsingum",
"Unfavorite" : "Taka úr eftirlætum",
"View in folder" : "Skoða í möppu",
"Type" : "Tegund",
"Created new folder \"{name}\"" : "Bjó til nýja möppu \"{name}\"",
@@ -327,6 +347,7 @@ OC.L10N.register(
"Templates" : "Sniðmát",
"New template folder" : "Ný mappa fyrir sniðmát",
"In folder" : "Í möppunni",
"Pick folder to search in" : "Veldu möppu til að leita í",
"Search in all files" : "Leita í öllum skrám",
"Search in folder: {folder}" : "Leita í möppunni: {folder}",
"One of the dropped files could not be processed" : "Ekki var hægt að vinna með eina af slepptu skránum",
+21
View File
@@ -77,6 +77,7 @@
"Go to the \"{dir}\" directory" : "Fara í heimamöppu",
"Current directory path" : "Fyrirliggjandi slóð að möppu",
"Share" : "Deila",
"Reload content" : "Endurlesa efni",
"Your have used your space quota and cannot upload files anymore" : "Þú hefur fullnýtt geymslukvótann þinn og getur ekki lengur sent inn skrár",
"You do not have permission to upload or create files here." : "Þú hefur ekki heimild til að senda inn eða búa til skrár hér.",
"Drag and drop files here to upload" : "Dragðu og slepptu hér skrám til að senda inn",
@@ -92,6 +93,7 @@
"Another entry with the same name already exists." : "Önnur færsla með sama heiti er þegar til staðar.",
"Invalid filename." : "Ógilt skráarheiti.",
"Rename file" : "Endurnefna skrá",
"Recently created" : "Nýlega útbúið",
"Folder" : "Mappa",
"Unknown file type" : "Óþekkt skráartegund",
"{ext} image" : "{ext} mynd",
@@ -107,6 +109,7 @@
"Last 30 days" : "Síðustu 30 daga",
"This year ({year})" : "Þetta ár ({year})",
"Last year ({year})" : "Síðasta ár ({year})",
"Custom range" : "Sérsniðið bil",
"Custom date range" : "Sérsniðið dagsetningabil",
"Search everywhere" : "Leita allsstaðar",
"Documents" : "Skjöl",
@@ -118,6 +121,7 @@
"Images" : "Myndir",
"Videos" : "Myndskeið",
"Filters" : "Síur",
"Back to filters" : "Til baka í síur",
"Appearance" : "Útlit",
"Show hidden files" : "Sýna faldar skrár",
"Show file type column" : "Sýna dálk fyrir skráategund",
@@ -126,6 +130,7 @@
"General" : "Almennt",
"Sort favorites first" : "Raða eftirlætum fremst",
"Sort folders before files" : "Raða möppum á undan skrám",
"Enable folder tree view" : "Virkja möppugreinasýn",
"Default view" : "Sjálfgefin sýn",
"All files" : "Allar skrár",
"Personal files" : "Einkaskrár",
@@ -148,7 +153,10 @@
"Show those shortcuts" : "Sýna þessa flýtilykla",
"Warnings" : "Aðvaranir",
"Warn before changing a file extension" : "Aðvara áður en skráarendingu er breytt",
"Warn before deleting a file" : "Aðvara áður en skrá er eytt",
"WebDAV URL" : "WebDAV-slóð",
"Create an app password" : "Búa til lykilorð forrits",
"Required for WebDAV authentication because Two-Factor Authentication is enabled for this account." : "Er krafist fyrir WebDAV-auðkenningu þar sem tveggja-þátta auðkenning er virkjuð fyrir þennan aðgang.",
"How to access files using WebDAV" : "Hvernig á að nálgast skrár í gegnum WebDAV",
"Total rows summary" : "Samantek á fjölda raða",
"Toggle selection for all files and folders" : "Víxla vali af/á fyrir allar skrár og möppur",
@@ -225,6 +233,9 @@
"Removing the file extension \"{old}\" may render the file unreadable." : "Sé skráaendingin \"{old}\" fjarlægð, gæti skráin orðið ólæsileg.",
"Adding the file extension \"{new}\" may render the file unreadable." : "Sé skráaendingunni \"{new}\" bætt við, gæti skráin orðið ólæsileg.",
"Do not show this dialog again." : "Ekki sýna þennan glugga aftur.",
"Rename file to hidden" : "Endurnefna skrá sem falda",
"Prefixing a filename with a dot may render the file hidden." : "Sé settur punktur framan við skráaheiti, gæti skráin orðið falin.",
"Are you sure you want to rename the file to \"{filename}\"?" : "Ertu viss um að þú viljir endurnefna skrána sem \"{filename}\"?",
"Cancel" : "Hætta við",
"Rename" : "Endurnefna",
"Select file or folder to link to" : "Veldu skrá eða möppu til að tengja í",
@@ -239,11 +250,13 @@
"Error during upload: {message}" : "Villa við innsendingu: {message}",
"Error during upload, status code {status}" : "Villa við innsendingu, stöðukóði: {status}",
"Unknown error during upload" : "Óþekkt villa við innsendingu",
"File list is reloading" : "Skráalisti er að hlaðast inn",
"Loading current folder" : "Hleð inn núverandi möppu",
"Retry" : "Reyna aftur",
"No files in here" : "Engar skrár hér",
"Upload some content or sync with your devices!" : "Sendu inn eitthvað efni eða samstilltu við tækin þín!",
"Go back" : "Fara til baka",
"Loading …" : "Hleð inn…",
"Your files" : "Skrárnar þínar",
"Open in files" : "Opna í skráaforritinu",
"File cannot be accessed" : "Skráin er ekki aðgengileg",
@@ -267,6 +280,9 @@
"Failed to convert files: {message}" : "Mistókst að umbreyta skrám: {message}",
"All files failed to be converted" : "Mistókst að umbreyta öllum skrám",
"One file could not be converted: {message}" : "Ekki var hægt að umbreyta einni skrá: {message}",
"_%n file could not be converted_::_%n files could not be converted_" : ["Ekki var hægt að umbreyta %n skrá","Ekki var hægt að umbreyta %n skrám"],
"_%n file converted_::_%n files converted_" : ["%n skrá umbreytt","%n skrám umbreytt"],
"Files converted" : "Skrám umbreytt",
"Failed to convert files" : "Mistókst að umbreyta skrám",
"Converting file …" : "Umbreyti skrá…",
"File successfully converted" : "Tókst að umbreyta skrá",
@@ -302,7 +318,9 @@
"The files are locked" : "Skrárnar eru læstar",
"The file does not exist anymore" : "Skráin er ekki lengur til",
"Moving \"{source}\" to \"{destination}\" …" : "Færi \"{source}\" í \"{destination}\" …",
"Moving {count} files to \"{destination}\" …" : "Færi {count} skrár í \"{destination}\" …",
"Copying \"{source}\" to \"{destination}\" …" : "Afrita \"{source}\" í \"{destination}\" …",
"Copying {count} files to \"{destination}\" …" : "Afrita {count} skrár í \"{destination}\" …",
"Choose destination" : "Veldu áfangastað",
"Copy to {target}" : "Afrita í {target}",
"Move to {target}" : "Færa í {target}",
@@ -317,6 +335,8 @@
"Retry and close" : "Prófa aftur og loka",
"Open online" : "Opna á netinu",
"Details" : "Nánar",
"Open the details sidebar" : "Opna hliðarspjald með ítarupplýsingum",
"Unfavorite" : "Taka úr eftirlætum",
"View in folder" : "Skoða í möppu",
"Type" : "Tegund",
"Created new folder \"{name}\"" : "Bjó til nýja möppu \"{name}\"",
@@ -325,6 +345,7 @@
"Templates" : "Sniðmát",
"New template folder" : "Ný mappa fyrir sniðmát",
"In folder" : "Í möppunni",
"Pick folder to search in" : "Veldu möppu til að leita í",
"Search in all files" : "Leita í öllum skrám",
"Search in folder: {folder}" : "Leita í möppunni: {folder}",
"One of the dropped files could not be processed" : "Ekki var hægt að vinna með eina af slepptu skránum",
+7 -2
View File
@@ -8,6 +8,7 @@
namespace OCA\Files\BackgroundJob;
use OC\Files\SetupManager;
use OC\Files\Utils\Scanner;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
@@ -15,6 +16,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
/**
@@ -33,6 +35,8 @@ class ScanFiles extends TimedJob {
private LoggerInterface $logger,
private IDBConnection $connection,
ITimeFactory $time,
private readonly SetupManager $setupManager,
private readonly IUserManager $userManager,
) {
parent::__construct($time);
// Run once per 10 minutes
@@ -42,10 +46,11 @@ class ScanFiles extends TimedJob {
protected function runScanner(string $user): void {
try {
$scanner = new Scanner(
$user,
$this->userManager->get($user),
null,
$this->dispatcher,
$this->logger
$this->logger,
$this->setupManager,
);
$scanner->backgroundScan('');
} catch (\Exception $e) {
+6 -3
View File
@@ -11,6 +11,7 @@ use OC\Core\Command\Base;
use OC\Core\Command\InterruptedException;
use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OC\Files\SetupManager;
use OC\Files\Storage\Wrapper\Jail;
use OC\Files\Utils\Scanner;
use OC\FilesMetadata\FilesMetadataManager;
@@ -49,6 +50,7 @@ class Scan extends Base {
private FilesMetadataManager $filesMetadataManager,
private IEventDispatcher $eventDispatcher,
private LoggerInterface $logger,
private SetupManager $setupManager,
) {
parent::__construct();
}
@@ -111,10 +113,11 @@ class Scan extends Base {
): void {
$connection = $this->reconnectToDatabase($output);
$scanner = new Scanner(
$user,
$this->userManager->get($user),
new ConnectionAdapter($connection),
Server::get(IEventDispatcher::class),
Server::get(LoggerInterface::class)
$this->eventDispatcher,
$this->logger,
$this->setupManager,
);
# check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
+2
View File
@@ -10,6 +10,7 @@ use OC\Core\Command\Base;
use OC\Core\Command\InterruptedException;
use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OC\Files\SetupManager;
use OC\Files\Utils\Scanner;
use OC\ForbiddenException;
use OC\Preview\Storage\StorageFactory;
@@ -60,6 +61,7 @@ class ScanAppData extends Base {
new ConnectionAdapter($connection),
Server::get(IEventDispatcher::class),
Server::get(LoggerInterface::class),
Server::get(SetupManager::class),
);
}
+1 -1
View File
@@ -12,8 +12,8 @@ import { emit } from '@nextcloud/event-bus'
import { n, t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import PQueue from 'p-queue'
import logger from '../logger.ts'
import { fetchNode } from '../services/WebdavClient.ts'
import { logger } from '../utils/logger.ts'
type ConversionResponse = {
path: string
+1 -1
View File
@@ -10,7 +10,7 @@ import * as capabilities from '@nextcloud/capabilities'
import * as eventBus from '@nextcloud/event-bus'
import { File, Folder, Permission } from '@nextcloud/files'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { action } from './deleteAction.ts'
import { shouldAskForConfirmation } from './deleteUtils.ts'
+1 -1
View File
@@ -12,7 +12,7 @@ import { Permission } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import PQueue from 'p-queue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, shouldAskForConfirmation } from './deleteUtils.ts'
// TODO: once the files app is migrated to the new frontend use the import instead:
+1 -1
View File
@@ -11,10 +11,10 @@ import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { DefaultType, FileType } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import logger from '../logger.ts'
import { useFilesStore } from '../store/files.ts'
import { getPinia } from '../store/index.ts'
import { usePathsStore } from '../store/paths.ts'
import { logger } from '../utils/logger.ts'
import { isDownloadable } from '../utils/permissions.ts'
export const action: IFileAction = {
@@ -9,7 +9,7 @@ import axios from '@nextcloud/axios'
import * as eventBus from '@nextcloud/event-bus'
import { File, Permission } from '@nextcloud/files'
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { action } from './favoriteAction.ts'
import * as favoriteAction from './favoriteAction.ts'
+1 -1
View File
@@ -16,7 +16,7 @@ import { generateUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'
import PQueue from 'p-queue'
import Vue from 'vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
const queue = new PQueue({ concurrency: 5 })
+1 -1
View File
@@ -19,8 +19,8 @@ import { t } from '@nextcloud/l10n'
import { getConflicts } from '@nextcloud/upload'
import { basename, join } from 'path'
import Vue from 'vue'
import logger from '../logger.ts'
import { getContents } from '../services/Files.ts'
import { logger } from '../utils/logger.ts'
import { canCopy, canMove, getQueue, MoveCopyAction } from './moveOrCopyActionUtils.ts'
/**
+1 -1
View File
@@ -14,7 +14,7 @@ import { translate as t } from '@nextcloud/l10n'
import { encodePath } from '@nextcloud/paths'
import { generateOcsUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { isSyncable } from '../utils/permissions.ts'
export const action: IFileAction = {
+1 -1
View File
@@ -7,7 +7,7 @@ import type { IView } from '@nextcloud/files'
import { File, Folder, Permission } from '@nextcloud/files'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { action } from './sidebarAction.ts'
const sidebar = vi.hoisted(() => ({
+1 -1
View File
@@ -9,7 +9,7 @@ import InformationSvg from '@mdi/svg/svg/information-outline.svg?raw'
import { getSidebar, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { isPublicShare } from '@nextcloud/sharing/public'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
export const ACTION_DETAILS = 'details'
+1 -1
View File
@@ -76,7 +76,6 @@ import NcBreadcrumbs from '@nextcloud/vue/components/NcBreadcrumbs'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useViews } from '../composables/useViews.ts'
import logger from '../logger.ts'
import { dataTransferToFileTree, onDropExternalFiles, onDropInternalFiles } from '../services/DropService.ts'
import { useActiveStore } from '../store/active.ts'
import { useDragAndDropStore } from '../store/dragging.ts'
@@ -84,6 +83,7 @@ import { useFilesStore } from '../store/files.ts'
import { usePathsStore } from '../store/paths.ts'
import { useSelectionStore } from '../store/selection.ts'
import { useUploaderStore } from '../store/uploader.ts'
import { logger } from '../utils/logger.ts'
export default defineComponent({
name: 'BreadCrumbs',
@@ -37,9 +37,9 @@ import { UploadStatus } from '@nextcloud/upload'
import debounce from 'debounce'
import { defineComponent } from 'vue'
import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
import logger from '../logger.ts'
import { dataTransferToFileTree, onDropExternalFiles } from '../services/DropService.ts'
import { useActiveStore } from '../store/active.ts'
import { logger } from '../utils/logger.ts'
export default defineComponent({
name: 'DragAndDropNotice',
@@ -136,10 +136,10 @@ import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import CustomElementRender from '../CustomElementRender.vue'
import { useFileListWidth } from '../../composables/useFileListWidth.ts'
import logger from '../../logger.ts'
import actionsMixins from '../../mixins/actionsMixin.ts'
import { useActiveStore } from '../../store/active.ts'
import { executeAction } from '../../utils/actionUtils.ts'
import { logger } from '../../utils/logger.ts'
export default defineComponent({
name: 'FileEntryActions',
@@ -27,10 +27,10 @@ import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { defineComponent } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import logger from '../../logger.ts'
import { useActiveStore } from '../../store/active.ts'
import { useKeyboardStore } from '../../store/keyboard.ts'
import { useSelectionStore } from '../../store/selection.ts'
import { logger } from '../../utils/logger.ts'
export default defineComponent({
name: 'FileEntryCheckbox',
@@ -49,11 +49,11 @@ import { basename } from '@nextcloud/paths'
import { defineComponent, inject } from 'vue'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import { useFileListWidth } from '../../composables/useFileListWidth.ts'
import logger from '../../logger.ts'
import { useActiveStore } from '../../store/active.ts'
import { useRenamingStore } from '../../store/renaming.ts'
import { useUserConfigStore } from '../../store/userconfig.ts'
import { getFilenameValidity } from '../../utils/filenameValidity.ts'
import { logger } from '../../utils/logger.ts'
export default defineComponent({
name: 'FileEntryName',
@@ -78,9 +78,9 @@ import CollectivesIcon from './CollectivesIcon.vue'
import FavoriteIcon from './FavoriteIcon.vue'
import RecentlyCreatedIcon from './RecentlyCreatedIcon.vue'
import { usePreviewImage } from '../../composables/usePreviewImage.ts'
import logger from '../../logger.ts'
import { isLivePhoto } from '../../services/LivePhotos.ts'
import { useUserConfigStore } from '../../store/userconfig.ts'
import { logger } from '../../utils/logger.ts'
export default defineComponent({
name: 'FileEntryPreview',
+12 -7
View File
@@ -14,13 +14,13 @@ import { generateUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'
import { getConflicts, getUploader } from '@nextcloud/upload'
import { vOnClickOutside } from '@vueuse/components'
import { extname } from 'path'
import { extname, relative } from 'path'
import Vue, { computed, defineComponent } from 'vue'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import logger from '../logger.ts'
import { onDropInternalFiles } from '../services/DropService.ts'
import { getDragAndDropPreview } from '../utils/dragUtils.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { logger } from '../utils/logger.ts'
import { isDownloadable } from '../utils/permissions.ts'
Vue.directive('onClickOutside', vOnClickOutside)
@@ -488,12 +488,17 @@ export default defineComponent({
const items = Array.from(event.dataTransfer?.items || [])
if (selection.length === 0 && items.some((item) => item.kind === 'file')) {
const files = items.filter((item) => item.kind === 'file')
.map((item) => 'webkitGetAsEntry' in item ? item.webkitGetAsEntry() : item.getAsFile())
.filter(Boolean) as (FileSystemEntry | File)[]
const uploader = getUploader()
const root = uploader.destination.path
const relativePath = relative(root, this.source.path)
logger.debug('Start uploading dropped files', { target: this.source.path, root, relativePath, files: files.map((file) => file.name) })
await uploader.batchUpload(
this.source.path,
items.filter((item) => item.kind === 'file')
.map((item) => 'webkitGetAsEntry' in item ? item.webkitGetAsEntry() : item.getAsFile())
.filter(Boolean) as (FileSystemEntry | File)[],
relativePath,
files,
async (nodes, path) => {
try {
const { contents, folder } = await this.activeView!.getContents(path)
@@ -536,7 +541,7 @@ export default defineComponent({
const isCopy = event.ctrlKey
this.dragover = false
logger.debug('Dropped', { event, folder: this.source, selection, fileTree })
logger.debug('Dropped', { event, folder: this.source, selection })
const nodes = selection.map((source) => this.filesStore.getNode(source)) as Node[]
await onDropInternalFiles(nodes, this.source, contents, isCopy)
@@ -13,7 +13,7 @@ import type { Folder, IFileListHeader, View } from '@nextcloud/files'
import type { PropType } from 'vue'
import PQueue from 'p-queue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
/**
* This component is used to render custom
@@ -84,11 +84,11 @@ import { FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID } from './FilesListTableHeaderActi
import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import logger from '../logger.ts'
import filesSortingMixin from '../mixins/filesSorting.ts'
import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import { logger } from '../utils/logger.ts'
export const FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID = 'files-list-header-select-all-checkbox'
@@ -88,12 +88,12 @@ import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import { FILES_LIST_HEADER_SELECT_ALL_CHECKBOX_ID } from './FilesListTableHeader.vue'
import { useFileActions } from '../composables/useFileActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import logger from '../logger.ts'
import actionsMixins from '../mixins/actionsMixin.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import { logger } from '../utils/logger.ts'
export const FILE_LIST_HEAD_FIRST_BATCH_ACTION_ID = 'files-list-head-first-batch-action'
@@ -91,10 +91,10 @@ import { useEnabledFileActions } from '../composables/useFileActions.ts'
import { useFileListHeaders } from '../composables/useFileListHeaders.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import logger from '../logger.ts'
import { useActiveStore } from '../store/active.ts'
import { useSelectionStore } from '../store/selection.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { logger } from '../utils/logger.ts'
export default defineComponent({
name: 'FilesListVirtual',
@@ -10,9 +10,9 @@ import { NcIconSvgWrapper, NcLoadingIcon } from '@nextcloud/vue'
import { ref, toRef, watch } from 'vue'
import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import logger from '../../logger.ts'
import { useActiveStore } from '../../store/active.ts'
import { useSidebarStore } from '../../store/sidebar.ts'
import { logger } from '../../utils/logger.ts'
const props = defineProps<{
/**
@@ -37,7 +37,7 @@ import { debounce, throttle } from 'throttle-debounce'
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
import NcProgressBar from '@nextcloud/vue/components/NcProgressBar'
import ChartPie from 'vue-material-design-icons/ChartPieOutline.vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
export default {
name: 'NavigationQuota',
@@ -17,8 +17,8 @@ import NcInputField from '@nextcloud/vue/components/NcInputField'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcProgressBar from '@nextcloud/vue/components/NcProgressBar'
import logger from '../../logger.ts'
import { SanitizeFilenameStatus } from '../../models/SanitizeFilenameStatus.ts'
import { logger } from '../../utils/logger.ts'
type ApiStatus = { total: number, processed: number, errors?: Record<string, string[]>, status: SanitizeFilenameStatus }
@@ -52,7 +52,7 @@ import debounce from 'debounce'
import Vue from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
const picker = getFilePickerBuilder(t('files', 'Choose a file or folder to transfer'))
.setMultiSelect(false)
+1 -1
View File
@@ -77,7 +77,7 @@ import type { PropType } from 'vue'
import debounce from 'debounce'
import { defineComponent } from 'vue'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
interface RecycledPoolItem {
key: string
+1 -1
View File
@@ -7,9 +7,9 @@ import { getFileActions } from '@nextcloud/files'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { dirname } from 'path'
import { useRoute, useRouter } from 'vue-router/composables'
import logger from '../logger.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { executeAction } from '../utils/actionUtils.ts'
import { logger } from '../utils/logger.ts'
import { useRouteParameters } from './useRouteParameters.ts'
/**
+1 -1
View File
@@ -11,7 +11,7 @@ import { t } from '@nextcloud/l10n'
import wrap from '@vue/web-component-wrapper'
import Vue from 'vue'
import FileListFilterType from '../components/FileListFilter/FileListFilterType.vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
export interface ITypePreset {
id: string
+1 -1
View File
@@ -13,7 +13,7 @@ import { emit } from '@nextcloud/event-bus'
import { Folder, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { basename } from 'path'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { newNodeName } from '../utils/newNodeDialog.ts'
export const entry: NewMenuEntry = {
+1 -1
View File
@@ -14,7 +14,7 @@ import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { join } from 'path'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { newNodeName } from '../utils/newNodeDialog.ts'
const templatesEnabled = loadState<boolean>('files', 'templates_enabled', true)
@@ -9,7 +9,7 @@ import { getFilePickerBuilder } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { translate as t } from '@nextcloud/l10n'
import { imagePath } from '@nextcloud/router'
import logger from '../../logger.ts'
import { logger } from '../../utils/logger.ts'
/**
* Initialize the unified search plugin.
+1 -1
View File
@@ -12,11 +12,11 @@ import { relative } from 'path'
import queryString from 'query-string'
import Vue from 'vue'
import Router, { isNavigationFailure, NavigationFailureType } from 'vue-router'
import logger from '../logger.ts'
import { useFilesStore } from '../store/files.ts'
import { getPinia } from '../store/index.ts'
import { usePathsStore } from '../store/paths.ts'
import { defaultView } from '../utils/filesViews.ts'
import { logger } from '../utils/logger.ts'
Vue.use(Router)
+2 -2
View File
@@ -13,7 +13,7 @@ import { join } from '@nextcloud/paths'
import { getUploader, hasConflict } from '@nextcloud/upload'
import { handleCopyMoveNodesTo, HintException } from '../actions/moveOrCopyAction.ts'
import { MoveCopyAction } from '../actions/moveOrCopyActionUtils.ts'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { createDirectoryIfNotExists, Directory, resolveConflict, traverseTree } from './DropServiceUtils.ts'
/**
@@ -123,7 +123,7 @@ export async function onDropExternalFiles(root: RootDirectory, destination: IFol
// then browse its tree and upload its contents.
if (file instanceof Directory) {
try {
logger.debug('Processing directory', { relativePath })
logger.debug('Processing directory', { relativePath, destination })
await createDirectoryIfNotExists(relativePath, destination)
await uploadDirectoryContents(file, relativePath)
} catch (error) {
@@ -6,7 +6,7 @@
import { join } from 'node:path'
import { beforeAll, describe, expect, it, vi } from 'vitest'
import { DataTransferItem as DataTransferItemMock, FileSystemDirectoryEntry, fileSystemEntryToDataTransferItem, FileSystemFileEntry } from '../../../../__tests__/FileSystemAPIUtils.ts'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { dataTransferToFileTree } from './DropService.ts'
import { Directory, traverseTree } from './DropServiceUtils.ts'
+1 -1
View File
@@ -12,7 +12,7 @@ import { defaultRemoteURL, defaultRootPath, getClient, getDefaultPropfind, resul
import { t } from '@nextcloud/l10n'
import { join } from '@nextcloud/paths'
import { openConflictPicker } from '@nextcloud/upload'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
/**
* This represents a Directory in the file tree
+1 -1
View File
@@ -8,7 +8,7 @@ import type { ContentsWithRoot } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission } from '@nextcloud/files'
import { getFavoriteNodes, getRemoteURL, getRootPath } from '@nextcloud/files/dav'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { getContents as filesContents } from './Files.ts'
import { client } from './WebdavClient.ts'
+1 -1
View File
@@ -7,10 +7,10 @@ import type { FileStat, ResponseDataDetailed } from 'webdav'
import { getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'
import { join } from 'path'
import logger from '../logger.ts'
import { useFilesStore } from '../store/files.ts'
import { getPinia } from '../store/index.ts'
import { useSearchStore } from '../store/search.ts'
import { logger } from '../utils/logger.ts'
import { client } from './WebdavClient.ts'
import { searchNodes } from './WebDavSearch.ts'
+1 -1
View File
@@ -9,9 +9,9 @@ import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission } from '@nextcloud/files'
import { getRecentSearch, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav'
import { loadState } from '@nextcloud/initial-state'
import logger from '../logger.ts'
import { getPinia } from '../store/index.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { logger } from '../utils/logger.ts'
import { client } from './WebdavClient.ts'
const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 14))
+1 -1
View File
@@ -8,9 +8,9 @@ import type { ContentsWithRoot } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { Folder, Permission } from '@nextcloud/files'
import { defaultRemoteURL, getRootPath } from '@nextcloud/files/dav'
import logger from '../logger.ts'
import { getPinia } from '../store/index.ts'
import { useSearchStore } from '../store/search.ts'
import { logger } from '../utils/logger.ts'
import { searchNodes } from './WebDavSearch.ts'
/**
+1 -1
View File
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { generateUrl, getRootUrl } from '@nextcloud/router'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
export default () => {
if ('serviceWorker' in navigator) {
+1 -1
View File
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
export default class Settings {
_settings
+1 -1
View File
@@ -9,7 +9,7 @@ import type { ResponseDataDetailed, SearchResult } from 'webdav'
import { getCurrentUser } from '@nextcloud/auth'
import { defaultRootPath, getDavNameSpaces, getDavProperties, resultToNode } from '@nextcloud/files/dav'
import { getBaseUrl } from '@nextcloud/router'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { client } from './WebdavClient.ts'
export interface SearchNodesOptions {
+1 -1
View File
@@ -11,7 +11,7 @@ import { Folder, getNavigation, Permission } from '@nextcloud/files'
import { getRemoteURL, getRootPath } from '@nextcloud/files/dav'
import { defineStore } from 'pinia'
import { ref, shallowRef, watch } from 'vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
// Temporary fake folder to use until we have the first valid folder
// fetched and cached. This allow us to mount the FilesListVirtual
+1 -1
View File
@@ -9,8 +9,8 @@ import type { FileSource, FilesStore, RootOptions, RootsStore, Service } from '.
import { subscribe } from '@nextcloud/event-bus'
import { defineStore } from 'pinia'
import Vue, { ref } from 'vue'
import logger from '../logger.ts'
import { fetchNode } from '../services/WebdavClient.ts'
import { logger } from '../utils/logger.ts'
import { usePathsStore } from './paths.ts'
/**
+1 -1
View File
@@ -9,7 +9,7 @@ import { emit, subscribe } from '@nextcloud/event-bus'
import { getFileListFilters, getFilesRegistry } from '@nextcloud/files'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
/**
* Check if the given value is an instance file list filter with mount function
+1 -1
View File
@@ -11,7 +11,7 @@ import { File, FileType, getNavigation } from '@nextcloud/files'
import { dirname } from '@nextcloud/paths'
import { defineStore } from 'pinia'
import Vue from 'vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { useFilesStore } from './files.ts'
/**
+1 -1
View File
@@ -13,8 +13,8 @@ import { basename, dirname, extname } from '@nextcloud/paths'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import { defineStore } from 'pinia'
import Vue, { defineAsyncComponent, ref } from 'vue'
import logger from '../logger.ts'
import { fetchNode } from '../services/WebdavClient.ts'
import { logger } from '../utils/logger.ts'
import { useUserConfigStore } from './userconfig.ts'
export const useRenamingStore = defineStore('renaming', () => {
+1 -1
View File
@@ -11,7 +11,7 @@ import { emit, subscribe } from '@nextcloud/event-bus'
import debounce from 'debounce'
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { VIEW_ID } from '../views/search.ts'
export const useSearchStore = defineStore('search', () => {
+1 -1
View File
@@ -9,7 +9,7 @@ import { subscribe } from '@nextcloud/event-bus'
import { getSidebarActions, getSidebarTabs } from '@nextcloud/files'
import { defineStore } from 'pinia'
import { computed, readonly, ref, watch } from 'vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
import { useActiveStore } from './active.ts'
import { useFilesStore } from './files.ts'
+1 -1
View File
@@ -8,8 +8,8 @@ import { showError, showSuccess } from '@nextcloud/dialogs'
import { NodeStatus } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import Vue from 'vue'
import logger from '../logger.ts'
import { useActiveStore } from '../store/active.ts'
import { logger } from '../utils/logger.ts'
/**
* Execute an action on the current active node
@@ -1,10 +1,11 @@
/**
/*!
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getLoggerBuilder } from '@nextcloud/logger'
export default getLoggerBuilder()
export const logger = getLoggerBuilder()
.setApp('files')
.detectUser()
.build()
@@ -15,7 +15,7 @@ import { FilePickerBuilder } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { onMounted } from 'vue'
import { generateFileUrl } from '../../../files_sharing/src/utils/generateUrl.ts'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
defineProps<{
providerId: string
+1 -1
View File
@@ -190,7 +190,6 @@ import FilesListVirtual from '../components/FilesListVirtual.vue'
import { useEnabledFileListActions } from '../composables/useFileListActions.ts'
import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useRouteParameters } from '../composables/useRouteParameters.ts'
import logger from '../logger.ts'
import filesSortingMixin from '../mixins/filesSorting.ts'
import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
@@ -204,6 +203,7 @@ import { useViewConfigStore } from '../store/viewConfig.ts'
import { humanizeWebDAVError } from '../utils/davUtils.ts'
import { defaultView } from '../utils/filesViews.ts'
import { getSummaryFor } from '../utils/fileUtils.ts'
import { logger } from '../utils/logger.ts'
export default defineComponent({
name: 'FilesList',
+1 -1
View File
@@ -52,9 +52,9 @@ import FilesNavigationSearch from '../components/FilesNavigationSearch.vue'
import NavigationQuota from '../components/NavigationQuota.vue'
import FilesAppSettings from './FilesAppSettings.vue'
import { useViews } from '../composables/useViews.ts'
import logger from '../logger.ts'
import { useActiveStore } from '../store/active.ts'
import { useSidebarStore } from '../store/sidebar.ts'
import { logger } from '../utils/logger.ts'
const sidebar = useSidebarStore()
const activeStore = useActiveStore()
+1 -1
View File
@@ -68,7 +68,7 @@ import { defineComponent } from 'vue'
import FileIcon from 'vue-material-design-icons/File.vue'
import FolderIcon from 'vue-material-design-icons/Folder.vue'
import { generateFileUrl } from '../../../files_sharing/src/utils/generateUrl.ts'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
// see lib/private/Collaboration/Reference/File/FileReferenceProvider.php
type Ressource = {
+1 -1
View File
@@ -13,7 +13,7 @@ import { ref } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
import SettingsSanitizeFilenames from '../components/Settings/SettingsSanitizeFilenames.vue'
import logger from '../logger.ts'
import { logger } from '../utils/logger.ts'
const {
docUrl,
+1 -1
View File
@@ -70,8 +70,8 @@ import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
import NcModal from '@nextcloud/vue/components/NcModal'
import TemplateFiller from '../components/TemplateFiller.vue'
import TemplatePreview from '../components/TemplatePreview.vue'
import logger from '../logger.ts'
import { createFromTemplate, getTemplateFields, getTemplates } from '../services/Templates.js'
import { logger } from '../utils/logger.ts'
const border = 2
const margin = 8
+1 -1
View File
@@ -10,9 +10,9 @@ import StarSvg from '@mdi/svg/svg/star-outline.svg?raw'
import { subscribe } from '@nextcloud/event-bus'
import { FileType, getNavigation, View } from '@nextcloud/files'
import { getCanonicalLocale, getLanguage, t } from '@nextcloud/l10n'
import logger from '../logger.ts'
import { getContents } from '../services/Favorites.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { logger } from '../utils/logger.ts'
/**
* Generate a favorite folder view
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace OCA\Files\Tests\BackgroundJob;
use OC\Files\Mount\MountPoint;
use OC\Files\SetupManager;
use OC\Files\Storage\Temporary;
use OCA\Files\BackgroundJob\ScanFiles;
use OCP\AppFramework\Utility\ITimeFactory;
@@ -17,6 +18,7 @@ use OCP\Files\Config\IUserMountCache;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Server;
use Psr\Log\LoggerInterface;
use Test\TestCase;
@@ -51,7 +53,9 @@ class ScanFilesTest extends TestCase {
$dispatcher,
$logger,
$connection,
$this->createMock(ITimeFactory::class)
$this->createMock(ITimeFactory::class),
$this->createMock(SetupManager::class),
$this->createMock(IUserManager::class),
])
->onlyMethods(['runScanner'])
->getMock();
+4
View File
@@ -15,6 +15,9 @@ OC.L10N.register(
"Unsatisfied authentication mechanism parameters" : "Neispunjeni parametri mehanizma autentifikacije",
"Insufficient data: %s" : "Nedovoljno podataka: %s",
"Storage with ID \"%d\" is not editable by non-admins" : "Pohranu s ID-om „%d” ne mogu uređivati korisnici koji nisu administratori",
"Static credentials" : "Statičke vjerodajnice",
"Access key ID" : "ID pristupnog ključa",
"Secret access key" : "Tajni pristupni ključ",
"Builtin" : "Ugrađen",
"None" : "Nema",
"OpenStack v2" : "OpenStack v2",
@@ -48,6 +51,7 @@ OC.L10N.register(
"Storage Class" : "Razred pohrane",
"Enable SSL" : "Omogući SSL",
"Enable Path Style" : "Omogući Path Style",
"Use Legacy S3 signing (v2)" : "Koristi starije S3 potpisivanje (v2)",
"Enable multipart copy" : "Omogući višedijelno kopiranje",
"Use presigned S3 url" : "Koristi unaprijed potpisani S3 URL",
"SSE-C encryption key" : "SSE-C enkripcijski ključ",
+4
View File
@@ -13,6 +13,9 @@
"Unsatisfied authentication mechanism parameters" : "Neispunjeni parametri mehanizma autentifikacije",
"Insufficient data: %s" : "Nedovoljno podataka: %s",
"Storage with ID \"%d\" is not editable by non-admins" : "Pohranu s ID-om „%d” ne mogu uređivati korisnici koji nisu administratori",
"Static credentials" : "Statičke vjerodajnice",
"Access key ID" : "ID pristupnog ključa",
"Secret access key" : "Tajni pristupni ključ",
"Builtin" : "Ugrađen",
"None" : "Nema",
"OpenStack v2" : "OpenStack v2",
@@ -46,6 +49,7 @@
"Storage Class" : "Razred pohrane",
"Enable SSL" : "Omogući SSL",
"Enable Path Style" : "Omogući Path Style",
"Use Legacy S3 signing (v2)" : "Koristi starije S3 potpisivanje (v2)",
"Enable multipart copy" : "Omogući višedijelno kopiranje",
"Use presigned S3 url" : "Koristi unaprijed potpisani S3 URL",
"SSE-C encryption key" : "SSE-C enkripcijski ključ",
+1
View File
@@ -148,6 +148,7 @@ OC.L10N.register(
"Can edit" : "Can edit",
"Custom permissions" : "Custom permissions",
"Resharing is not allowed" : "Resharing is not allowed",
"Searching …" : "ძებნა …",
"No elements found." : "No elements found.",
"Search everywhere" : "მოძებნე ყველგან",
"Guest" : "Guest",
+1
View File
@@ -146,6 +146,7 @@
"Can edit" : "Can edit",
"Custom permissions" : "Custom permissions",
"Resharing is not allowed" : "Resharing is not allowed",
"Searching …" : "ძებნა …",
"No elements found." : "No elements found.",
"Search everywhere" : "მოძებნე ყველგან",
"Guest" : "Guest",
+89 -31
View File
@@ -299,18 +299,65 @@ class Trashbin implements IEventListener {
$configuredTrashbinSize = static::getConfiguredTrashbinSize($owner);
if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) {
$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
return false;
}
try {
$moveSuccessful = true;
// there is still a possibility that the file has been deleted by a remote user
$deletedBy = self::overwriteDeletedBy($user);
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->insert('files_trash')
->setValue('id', $query->createNamedParameter($filename))
->setValue('timestamp', $query->createNamedParameter($timestamp))
->setValue('location', $query->createNamedParameter($location))
->setValue('user', $query->createNamedParameter($owner))
->setValue('deleted_by', $query->createNamedParameter($deletedBy));
$inserted = false;
try {
$inserted = ($query->executeStatement() === 1);
} catch (\Throwable $e) {
Server::get(LoggerInterface::class)->error(
'trash bin database insert failed',
[
'app' => 'files_trashbin',
'exception' => $e,
'user' => $owner,
'filename' => $filename,
'timestamp' => $timestamp,
]
);
}
if (!$inserted) {
Server::get(LoggerInterface::class)->error(
'trash bin database couldn\'t be updated, skipping trash move',
[
'app' => 'files_trashbin',
'user' => $owner,
'filename' => $filename,
'timestamp' => $timestamp,
]
);
$trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
return false;
}
$moveSuccessful = true;
try {
$inCache = $sourceStorage->getCache()->inCache($sourceInternalPath);
$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
if ($inCache) {
$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
} else {
$sizeDifference = $sourceInfo->getSize();
if ($sizeDifference < 0) {
$sizeDifference = null;
} else {
$sizeDifference = (int)$sizeDifference;
}
$trashStorage->getUpdater()->update($trashInternalPath, null, $sizeDifference);
}
} catch (CopyRecursiveException $e) {
} catch (\Exception $e) {
$moveSuccessful = false;
if ($trashStorage->file_exists($trashInternalPath)) {
$trashStorage->unlink($trashInternalPath);
@@ -331,24 +378,31 @@ class Trashbin implements IEventListener {
} else {
$trashStorage->getUpdater()->remove($trashInternalPath);
}
return false;
$moveSuccessful = false;
}
if (!$moveSuccessful) {
Server::get(LoggerInterface::class)->error(
'trash move failed, removing trash metadata and payload',
[
'app' => 'files_trashbin',
'user' => $owner,
'filename' => $filename,
'timestamp' => $timestamp,
]
);
self::deleteTrashRow($user, $filename, $timestamp);
if ($trashStorage->file_exists($trashInternalPath)) {
if ($trashStorage->is_dir($trashInternalPath)) {
$trashStorage->rmdir($trashInternalPath);
} else {
$trashStorage->unlink($trashInternalPath);
}
}
$trashStorage->getUpdater()->remove($trashInternalPath);
}
if ($moveSuccessful) {
// there is still a possibility that the file has been deleted by a remote user
$deletedBy = self::overwriteDeletedBy($user);
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->insert('files_trash')
->setValue('id', $query->createNamedParameter($filename))
->setValue('timestamp', $query->createNamedParameter($timestamp))
->setValue('location', $query->createNamedParameter($location))
->setValue('user', $query->createNamedParameter($owner))
->setValue('deleted_by', $query->createNamedParameter($deletedBy));
$result = $query->executeStatement();
if (!$result) {
Server::get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
}
Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path),
'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]);
@@ -545,12 +599,7 @@ class Trashbin implements IEventListener {
self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
if ($timestamp) {
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->delete('files_trash')
->where($query->expr()->eq('user', $query->createNamedParameter($user)))
->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
$query->executeStatement();
self::deleteTrashRow($user, $filename, $timestamp);
}
return true;
@@ -689,13 +738,6 @@ class Trashbin implements IEventListener {
$size = 0;
if ($timestamp) {
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->delete('files_trash')
->where($query->expr()->eq('user', $query->createNamedParameter($user)))
->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
$query->executeStatement();
$file = static::getTrashFilename($filename, $timestamp);
} else {
$file = $filename;
@@ -706,6 +748,9 @@ class Trashbin implements IEventListener {
try {
$node = $userRoot->get('/files_trashbin/files/' . $file);
} catch (NotFoundException $e) {
if ($timestamp) {
self::deleteTrashRow($user, $filename, $timestamp);
}
return $size;
}
@@ -719,9 +764,22 @@ class Trashbin implements IEventListener {
$node->delete();
self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
if ($timestamp) {
self::deleteTrashRow($user, $filename, $timestamp);
}
return $size;
}
private static function deleteTrashRow(string $user, string $filename, int $timestamp): void {
$query = Server::get(IDBConnection::class)->getQueryBuilder();
$query->delete('files_trash')
->where($query->expr()->eq('user', $query->createNamedParameter($user)))
->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename)))
->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp)));
$query->executeStatement();
}
/**
* @param string $file
* @param string $filename
+83
View File
@@ -6,16 +6,21 @@ declare(strict_types=1);
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Files_Trashbin\Tests;
use OC\Files\Cache\Updater;
use OC\Files\Filesystem;
use OC\Files\ObjectStore\ObjectStoreStorage;
use OC\Files\Storage\Common;
use OC\Files\Storage\Local;
use OC\Files\Storage\Temporary;
use OC\Files\View;
use OCA\Files_Trashbin\AppInfo\Application;
use OCA\Files_Trashbin\Events\MoveToTrashEvent;
use OCA\Files_Trashbin\Storage;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCA\Files_Trashbin\Trashbin;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Constants;
@@ -118,6 +123,84 @@ class StorageTest extends \Test\TestCase {
$this->assertEquals('test.txt', substr($name, 0, strrpos($name, '.')));
}
public function testTrashEntryCreatedWhenSourceNotInCache(): void {
$this->userView->file_put_contents('uncached.txt', 'foo');
[$storage, $internalPath] = $this->userView->resolvePath('uncached.txt');
if ($storage->instanceOfStorage(ObjectStoreStorage::class)) {
$this->markTestSkipped('object store always has the file in cache');
}
$cache = $storage->getCache();
$cache->remove($internalPath);
$this->assertFalse($cache->inCache($internalPath));
$this->userView->unlink('uncached.txt');
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
$this->assertCount(1, $results);
$name = $results[0]->getName();
$this->assertEquals('uncached.txt', substr($name, 0, strrpos($name, '.')));
[$trashStorage, $trashInternalPath] = $this->rootView->resolvePath('/' . $this->user . '/files_trashbin/files/' . $name);
$this->assertTrue($trashStorage->getCache()->inCache($trashInternalPath));
}
public function testTrashEntryNotCreatedWhenDeleteFailed(): void {
$storage2 = $this->getMockBuilder(Temporary::class)
->setConstructorArgs([])
->onlyMethods(['unlink', 'instanceOfStorage'])
->getMock();
$storage2->method('unlink')
->willReturn(false);
// disable same-storage move optimization
$storage2->method('instanceOfStorage')
->willReturnCallback(fn (string $class) => ($class !== Local::class) && (new Temporary([]))->instanceOfStorage($class));
Filesystem::mount($storage2, [], $this->user . '/files/substorage');
$this->userView->file_put_contents('substorage/test.txt', 'foo');
$this->assertFalse($this->userView->unlink('substorage/test.txt'));
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
$this->assertEmpty($results);
$trashData = Trashbin::getExtraData($this->user);
$this->assertEmpty($trashData);
}
public function testTrashEntryNotCreatedWhenCacheRowFailed(): void {
$trashStorage = $this->getMockBuilder(Temporary::class)
->setConstructorArgs([])
->onlyMethods(['getUpdater'])
->getMock();
$updater = $this->getMockBuilder(Updater::class)
->setConstructorArgs([$trashStorage])
->onlyMethods(['renameFromStorage'])
->getMock();
$trashStorage->method('getUpdater')
->willReturn($updater);
$updater->method('renameFromStorage')
->willThrowException(new \Exception());
Filesystem::mount($trashStorage, [], $this->user . '/files_trashbin');
$this->userView->file_put_contents('test.txt', 'foo');
try {
$this->assertFalse($this->userView->unlink('test.txt'));
$this->fail();
} catch (\Exception) {
// expected
}
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
$this->assertEmpty($results);
$trashData = Trashbin::getExtraData($this->user);
$this->assertEmpty($trashData);
}
/**
* Test that deleting a folder puts it into the trashbin.
*/
+1
View File
@@ -2,6 +2,7 @@ OC.L10N.register(
"profile",
{
"Profile" : "Profile",
"Searching …" : "ძებნა …",
"Not found" : "Not found",
"You have not added any info yet" : "You have not added any info yet",
"{user} has not added any info yet" : "{user} has not added any info yet",
+1
View File
@@ -1,5 +1,6 @@
{ "translations": {
"Profile" : "Profile",
"Searching …" : "ძებნა …",
"Not found" : "Not found",
"You have not added any info yet" : "You have not added any info yet",
"{user} has not added any info yet" : "{user} has not added any info yet",
+36 -29
View File
@@ -369,6 +369,38 @@ $content-max-width: 640px;
}
}
.user-actions {
display: flex;
flex-direction: column;
gap: 8px 0;
margin-top: 20px;
max-width: 300px;
&__primary {
margin: 0 auto;
max-width: 100%;
&__icon {
filter: var(--primary-invert-if-dark);
}
}
&__other {
display: flex;
justify-content: center;
gap: 0 4px;
&__icon {
height: 20px;
width: 20px;
object-fit: contain;
filter: var(--background-invert-if-dark);
align-self: center;
margin: 12px; // so we get 44px x 44px
}
}
}
@media only screen and (max-width: 1024px) {
.profile {
&__header {
@@ -419,37 +451,12 @@ $content-max-width: 640px;
position: unset;
}
}
}
.user-actions {
display: flex;
flex-direction: column;
gap: 8px 0;
margin-top: 20px;
max-width: 300px;
&__primary {
.user-actions {
width: unset;
max-width: 600px;
margin: 0 auto;
max-width: 100%;
&__icon {
filter: var(--primary-invert-if-dark);
}
}
&__other {
display: flex;
justify-content: center;
gap: 0 4px;
&__icon {
height: 20px;
width: 20px;
object-fit: contain;
filter: var(--background-invert-if-dark);
align-self: center;
margin: 12px; // so we get 44px x 44px
}
padding: 20px 50px 0px 50px;
}
}
</style>
+2 -7
View File
@@ -37,7 +37,6 @@
:extra-props="{
users,
settings,
hasObfuscated,
quotaOptions,
languages,
externalActions,
@@ -50,7 +49,7 @@
</template>
<template #header>
<UserListHeader :has-obfuscated="hasObfuscated" />
<UserListHeader />
</template>
<template #footer>
@@ -77,7 +76,7 @@ import UserListHeader from './Users/UserListHeader.vue'
import UserRow from './Users/UserRow.vue'
import VirtualList from './Users/VirtualList.vue'
import logger from '../logger.ts'
import { defaultQuota, isObfuscated, unlimitedQuota } from '../utils/userUtils.ts'
import { defaultQuota, unlimitedQuota } from '../utils/userUtils.ts'
const newUser = Object.freeze({
id: '',
@@ -159,10 +158,6 @@ export default {
}
},
hasObfuscated() {
return this.filteredUsers.some((user) => isObfuscated(user))
},
users() {
return this.$store.getters.getUsers
},
@@ -29,13 +29,6 @@
{{ t('settings', 'Account name') }}
</span>
</th>
<th
class="header__cell"
:class="{ 'header__cell--obfuscated': hasObfuscated }"
data-cy-user-list-header-password
scope="col">
<span>{{ passwordLabel }}</span>
</th>
<th
class="header__cell"
data-cy-user-list-header-email
@@ -43,7 +36,7 @@
<span>{{ t('settings', 'Email') }}</span>
</th>
<th
class="header__cell header__cell--large"
class="header__cell header__cell--groups"
data-cy-user-list-header-groups
scope="col">
<span>{{ t('settings', 'Groups') }}</span>
@@ -121,13 +114,6 @@ import Vue from 'vue'
export default Vue.extend({
name: 'UserListHeader',
props: {
hasObfuscated: {
type: Boolean,
required: true,
},
},
computed: {
showConfig() {
// @ts-expect-error: allow untyped $store
@@ -138,14 +124,6 @@ export default Vue.extend({
// @ts-expect-error: allow untyped $store
return this.$store.getters.getServerData
},
passwordLabel(): string {
if (this.hasObfuscated) {
// TRANSLATORS This string is for a column header labelling either a password or a message that the current user has insufficient permissions
return t('settings', 'Password or insufficient permissions message')
}
return t('settings', 'Password')
},
},
methods: {
+1 -61
View File
@@ -49,36 +49,6 @@
<span class="row__subtitle">{{ user.id }}</span>
</td>
<td
data-cy-user-list-cell-password
class="row__cell"
:class="{ 'row__cell--obfuscated': hasObfuscated }">
<template v-if="editing && settings.canChangePassword && user.backendCapabilities.setPassword">
<NcTextField
v-model="editedPassword"
class="user-row-text-field"
data-cy-user-list-input-password
:data-loading="loading.password || undefined"
:trailing-button-label="t('settings', 'Submit')"
:class="{ 'icon-loading-small': loading.password }"
:show-trailing-button="true"
:disabled="loading.password || isLoadingField"
:minlength="minPasswordLength"
maxlength="469"
:label="t('settings', 'Set new password')"
trailing-button-icon="arrowEnd"
autocapitalize="off"
autocomplete="new-password"
required
spellcheck="false"
type="password"
@trailing-button-click="updatePassword" />
</template>
<span v-else-if="isObfuscated">
{{ t('settings', 'You do not have permissions to see the details of this account') }}
</span>
</td>
<td class="row__cell" data-cy-user-list-cell-email>
<template v-if="editing">
<NcTextField
@@ -105,7 +75,7 @@
</span>
</td>
<td class="row__cell row__cell--large row__cell--multiline" data-cy-user-list-cell-groups>
<td class="row__cell row__cell--groups row__cell--multiline" data-cy-user-list-cell-groups>
<template v-if="editing">
<label
class="hidden-visually"
@@ -361,11 +331,6 @@ export default {
required: true,
},
hasObfuscated: {
type: Boolean,
required: true,
},
quotaOptions: {
type: Array,
required: true,
@@ -398,7 +363,6 @@ export default {
loading: {
all: false,
displayName: false,
password: false,
mailAddress: false,
groups: false,
groupsDetails: false,
@@ -413,7 +377,6 @@ export default {
},
editedDisplayName: this.user.displayname,
editedPassword: '',
editedMail: this.user.email ?? '',
// Cancelable promise for search groups request
promise: null,
@@ -768,29 +731,6 @@ export default {
}
},
/**
* Set user password
*/
async updatePassword() {
this.loading.password = true
if (this.editedPassword.length === 0) {
showError(t('settings', "Password can't be empty"))
this.loading.password = false
} else {
try {
await this.$store.dispatch('setUserData', {
userid: this.user.id,
key: 'password',
value: this.editedPassword,
})
this.editedPassword = ''
showSuccess(t('settings', 'Password was successfully changed'))
} finally {
this.loading.password = false
}
}
},
/**
* Set user mailAddress
*/
@@ -157,6 +157,7 @@ export default Vue.extend({
--cell-padding: 7px;
--cell-width: 200px;
--cell-width-large: 300px;
--cell-width-groups: 380px;
--cell-min-width: calc(var(--cell-width) - (2 * var(--cell-padding)));
--sticky-column-z-index: calc(var(--vs-dropdown-z-index) + 1); // Keep the sticky column on top of the select dropdown
@@ -79,6 +79,11 @@
width: var(--cell-width-large);
}
&--groups {
min-width: var(--cell-width-groups);
width: var(--cell-width-groups);
}
&--obfuscated {
min-width: 400px;
width: 400px;
@@ -9,20 +9,22 @@
<p class="settings-hint hidden-when-empty">
{{ t('settings', 'Set up your account for passwordless authentication following the FIDO2 standard.') }}
</p>
<NcNoteCard v-if="devices.length === 0" type="info">
{{ t('settings', 'No devices configured.') }}
</NcNoteCard>
<h3 v-else id="security-webauthn__active-devices">
{{ t('settings', 'The following devices are configured for your account:') }}
</h3>
<ul aria-labelledby="security-webauthn__active-devices" class="security-webauthn__device-list">
<WebAuthnDevice
v-for="device in sortedDevices"
:key="device.id"
:name="device.name"
@delete="deleteDevice(device.id)" />
</ul>
<template v-else>
<h3 id="security-webauthn__active-devices">
{{ t('settings', 'The following devices are configured for your account:') }}
</h3>
<ul aria-labelledby="security-webauthn__active-devices" class="security-webauthn__device-list">
<WebAuthnDevice
v-for="device in sortedDevices"
:key="device.id"
:name="device.name"
@delete="deleteDevice(device.id)" />
</ul>
</template>
<NcNoteCard v-if="!supportsWebauthn" type="warning">
{{ t('settings', 'Your browser does not support WebAuthn.') }}
+1
View File
@@ -22,6 +22,7 @@ OC.L10N.register(
"Note:" : "Napomena:",
"This share is valid until %s at midnight" : "Ovo dijeljenje vrijedi do %s u ponoć",
"Expiration:" : "Istječe:",
"Open shared item" : "Otvori dijeljenu stavku",
"%1$s via %2$s" : "%1$s putem %2$s",
"%1$s shared %2$s with you. You should have already received a separate mail with a link to access it." : "%1$s je s vama podijelio/la %2$s. Već ste trebali zaprimiti zasebnu poruku e-pošte s poveznicom za pristup.",
"Password to access %1$s shared to you by %2$s" : "Zaporka za pristup %1$s koji je s vama podijelio/la %2$s",
+1
View File
@@ -20,6 +20,7 @@
"Note:" : "Napomena:",
"This share is valid until %s at midnight" : "Ovo dijeljenje vrijedi do %s u ponoć",
"Expiration:" : "Istječe:",
"Open shared item" : "Otvori dijeljenu stavku",
"%1$s via %2$s" : "%1$s putem %2$s",
"%1$s shared %2$s with you. You should have already received a separate mail with a link to access it." : "%1$s je s vama podijelio/la %2$s. Već ste trebali zaprimiti zasebnu poruku e-pošte s poveznicom za pristup.",
"Password to access %1$s shared to you by %2$s" : "Zaporka za pristup %1$s koji je s vama podijelio/la %2$s",
+1
View File
@@ -22,6 +22,7 @@ OC.L10N.register(
"Note:" : "Athugaðu:",
"This share is valid until %s at midnight" : "Þessi sameign gildir til %s á miðnætti",
"Expiration:" : "Gildistími:",
"Open shared item" : "Opna deilt atriði",
"%1$s via %2$s" : "%1$s með %2$s",
"%1$s shared %2$s with you. You should have already received a separate mail with a link to access it." : "%1$s deildi %2$s með þér. Þú ættir að hafa fengið sérstakan tölvupóst með tengli sem vísar á gögnin.",
"Password to access %1$s shared to you by %2$s" : "Lykilorði fyrir aðgang að %1$s var deilt með þér af %2$s",
+1
View File
@@ -20,6 +20,7 @@
"Note:" : "Athugaðu:",
"This share is valid until %s at midnight" : "Þessi sameign gildir til %s á miðnætti",
"Expiration:" : "Gildistími:",
"Open shared item" : "Opna deilt atriði",
"%1$s via %2$s" : "%1$s með %2$s",
"%1$s shared %2$s with you. You should have already received a separate mail with a link to access it." : "%1$s deildi %2$s með þér. Þú ættir að hafa fengið sérstakan tölvupóst með tengli sem vísar á gögnin.",
"Password to access %1$s shared to you by %2$s" : "Lykilorði fyrir aðgang að %1$s var deilt með þér af %2$s",
+1
View File
@@ -22,6 +22,7 @@ OC.L10N.register(
"Note:" : "備註:",
"This share is valid until %s at midnight" : "此分享有效期限至 %s 午夜",
"Expiration:" : "過期於:",
"Open shared item" : "開啟分享項目",
"%1$s via %2$s" : "%1$s 經由 %2$s",
"%1$s shared %2$s with you. You should have already received a separate mail with a link to access it." : "%1$s 與您分享了 %2$s。您應該已經收到一封包含了可以存取它的連結的單獨郵件。",
"Password to access %1$s shared to you by %2$s" : "存取 %1$s 的密碼已透過 %2$s 與您分享",
+1
View File
@@ -20,6 +20,7 @@
"Note:" : "備註:",
"This share is valid until %s at midnight" : "此分享有效期限至 %s 午夜",
"Expiration:" : "過期於:",
"Open shared item" : "開啟分享項目",
"%1$s via %2$s" : "%1$s 經由 %2$s",
"%1$s shared %2$s with you. You should have already received a separate mail with a link to access it." : "%1$s 與您分享了 %2$s。您應該已經收到一封包含了可以存取它的連結的單獨郵件。",
"Password to access %1$s shared to you by %2$s" : "存取 %1$s 的密碼已透過 %2$s 與您分享",
+1
View File
@@ -61,6 +61,7 @@ OC.L10N.register(
"Update" : "Uppfæra",
"Delete" : "Eyða",
"Reset" : "Endurstilla",
"Loading …" : "Hleð inn…",
"{displayName} (hidden)" : "{displayName} (falið)",
"{displayName} (restricted)" : "{displayName} (takmarkað)",
"Failed to apply tags changes" : "Mistókst að virkja breytingar á merkjum",
+1
View File
@@ -59,6 +59,7 @@
"Update" : "Uppfæra",
"Delete" : "Eyða",
"Reset" : "Endurstilla",
"Loading …" : "Hleð inn…",
"{displayName} (hidden)" : "{displayName} (falið)",
"{displayName} (restricted)" : "{displayName} (takmarkað)",
"Failed to apply tags changes" : "Mistókst að virkja breytingar á merkjum",
+1
View File
@@ -38,6 +38,7 @@ OC.L10N.register(
"%s (restricted)" : "%s (restricted)",
"%s (invisible)" : "%s (invisible)",
"<strong>System tags</strong> for a file have been modified" : "<strong>System tags</strong> for a file have been modified",
"Files" : "ფაილები",
"Tags" : "Tags",
"All tagged %s …" : "All tagged %s …",
"tagged %s" : "tagged %s",
+1
View File
@@ -36,6 +36,7 @@
"%s (restricted)" : "%s (restricted)",
"%s (invisible)" : "%s (invisible)",
"<strong>System tags</strong> for a file have been modified" : "<strong>System tags</strong> for a file have been modified",
"Files" : "ფაილები",
"Tags" : "Tags",
"All tagged %s …" : "All tagged %s …",
"tagged %s" : "tagged %s",
@@ -101,7 +101,7 @@ describe('Inline system tags action render tests', () => {
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
expect(result!.outerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"></ul>"')
expect(result!.outerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"></div>"')
})
test('Render a single system tag', async () => {
@@ -126,7 +126,7 @@ describe('Inline system tags action render tests', () => {
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
expect(result!.outerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Confidential">Confidential</li></ul>"')
expect(result!.outerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Confidential">Confidential</li></ul></div>"')
})
test('Render two system tags', async () => {
@@ -151,7 +151,7 @@ describe('Inline system tags action render tests', () => {
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
expect(result!.outerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Important">Important</li><li class="files-list__system-tag" data-systemtag-name="Confidential">Confidential</li></ul>"')
expect(result!.outerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Important">Important</li><li class="files-list__system-tag" data-systemtag-name="Confidential">Confidential</li></ul></div>"')
})
test('Render multiple system tags', async () => {
@@ -181,7 +181,7 @@ describe('Inline system tags action render tests', () => {
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
expect(result!.outerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Important">Important</li><li class="files-list__system-tag files-list__system-tag--more" data-systemtag-name="+3" title="Confidential, Secret, Classified" aria-hidden="true" role="presentation">+3</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Confidential">Confidential</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Secret">Secret</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Classified">Classified</li></ul>"')
expect(result!.outerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Important">Important</li><li class="files-list__system-tag files-list__system-tag--more" data-systemtag-name="+3" title="Confidential, Secret, Classified" aria-hidden="true" role="presentation">+3</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Confidential">Confidential</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Secret">Secret</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Classified">Classified</li></ul></div>"')
})
test('Render gets updated on system tag change', async () => {
@@ -212,7 +212,7 @@ describe('Inline system tags action render tests', () => {
}) as HTMLElement
document.body.appendChild(result)
expect(result).toBeInstanceOf(HTMLElement)
expect(document.body.innerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Important">Important</li><li class="files-list__system-tag files-list__system-tag--more" data-systemtag-name="+3" title="Confidential, Secret, Classified" aria-hidden="true" role="presentation">+3</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Confidential">Confidential</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Secret">Secret</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Classified">Classified</li></ul>"')
expect(document.body.innerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Important">Important</li><li class="files-list__system-tag files-list__system-tag--more" data-systemtag-name="+3" title="Confidential, Secret, Classified" aria-hidden="true" role="presentation">+3</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Confidential">Confidential</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Secret">Secret</li><li class="files-list__system-tag hidden-visually" data-systemtag-name="Classified">Classified</li></ul></div>"')
// Subscribe to the event
const eventPromise = new Promise((resolve) => {
@@ -229,7 +229,7 @@ describe('Inline system tags action render tests', () => {
// Wait for the event to be processed
await eventPromise
expect(document.body.innerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Public">Public</li></ul>"')
expect(document.body.innerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Public">Public</li></ul></div>"')
})
})
@@ -273,7 +273,7 @@ describe('Inline system tags action colors', () => {
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
expect(result!.outerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #000000;" data-systemtag-color="true">Confidential</li></ul>"')
expect(result!.outerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #000000;" data-systemtag-color="true">Confidential</li></ul></div>"')
})
test('Render a single system tag with invalid WCAG color', async () => {
@@ -300,7 +300,7 @@ describe('Inline system tags action colors', () => {
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
expect(result!.outerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #646464;" data-systemtag-color="true">Confidential</li></ul>"')
expect(result!.outerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #646464;" data-systemtag-color="true">Confidential</li></ul></div>"')
document.body.removeAttribute('data-themes')
})
@@ -328,7 +328,7 @@ describe('Inline system tags action colors', () => {
}) as HTMLElement
document.body.appendChild(result)
expect(result).toBeInstanceOf(HTMLElement)
expect(document.body.innerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #000000;" data-systemtag-color="true">Confidential</li></ul>"')
expect(document.body.innerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #000000;" data-systemtag-color="true">Confidential</li></ul></div>"')
// Subscribe to the event
const eventPromise = new Promise((resolve) => {
@@ -344,6 +344,6 @@ describe('Inline system tags action colors', () => {
// Wait for the event to be processed
await eventPromise
expect(document.body.innerHTML).toMatchInlineSnapshot('"<ul class="files-list__system-tags" aria-label="Assigned collaborative tags" data-systemtags-fileid="1"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #456789;" data-systemtag-color="true">Confidential</li></ul>"')
expect(document.body.innerHTML).toMatchInlineSnapshot('"<div data-systemtags-fileid="1"><ul class="files-list__system-tags" aria-label="Assigned collaborative tags"><li class="files-list__system-tag" data-systemtag-name="Confidential" style="--systemtag-color: #456789;" data-systemtag-color="true">Confidential</li></ul></div>"')
})
})
@@ -34,12 +34,15 @@ export const action: IFileAction = {
return true
},
exec: async () => null,
renderInline: ({ nodes }) => {
async exec() {
return null
},
async renderInline({ nodes }) {
if (nodes.length !== 1 || !nodes[0]) {
return Promise.resolve(null)
return null
}
return renderInline(nodes[0])
return await renderInline(nodes[0])
},
order: 0,
@@ -56,12 +59,12 @@ subscribe('systemtags:tag:updated', updateTag)
*
* @param node - The updated node
*/
function updateSystemTagsHtml(node: INode) {
renderInline(node).then((systemTagsHtml) => {
document.querySelectorAll(`[data-systemtags-fileid="${node.fileid}"]`).forEach((element) => {
element.replaceWith(systemTagsHtml)
})
})
async function updateSystemTagsHtml(node: INode) {
const systemTagsHtml = await renderInline(node)
const elements = document.querySelectorAll(`[data-systemtags-fileid="${node.id}"]`)
for (const element of elements) {
element.replaceWith(systemTagsHtml)
}
}
/**
@@ -145,50 +148,50 @@ function renderTag(tag: string, isMore = false): HTMLElement {
async function renderInline(node: INode): Promise<HTMLElement> {
// Ensure we have the system tags as an array
const tags = getNodeSystemTags(node)
const systemTagsElementWrapper = document.createElement('div')
systemTagsElementWrapper.setAttribute('data-systemtags-fileid', node.id || '')
const systemTagsElement = document.createElement('ul')
systemTagsElement.classList.add('files-list__system-tags')
systemTagsElement.setAttribute('aria-label', t('files', 'Assigned collaborative tags'))
systemTagsElement.setAttribute('data-systemtags-fileid', node.fileid?.toString() || '')
if (tags.length > 0) {
const systemTagsElement = document.createElement('ul')
systemTagsElement.classList.add('files-list__system-tags')
systemTagsElement.setAttribute('aria-label', t('files', 'Assigned collaborative tags'))
systemTagsElementWrapper.appendChild(systemTagsElement)
if (tags.length === 0) {
return systemTagsElement
}
// Fetch the tags if the cache is empty
if (cache.length === 0) {
try {
// Best would be to support attributes from webdav,
// but currently the library does not support it
cache.push(...await fetchTags())
} catch (error) {
logger.error('Failed to fetch tags', { error })
}
}
// Fetch the tags if the cache is empty
if (cache.length === 0) {
try {
// Best would be to support attributes from webdav,
// but currently the library does not support it
cache.push(...await fetchTags())
} catch (error) {
logger.error('Failed to fetch tags', { error })
systemTagsElement.append(renderTag(tags[0]!))
if (tags.length === 2) {
// Special case only two tags:
// the overflow fake tag would take the same space as this, so render it
systemTagsElement.append(renderTag(tags[1]!))
} else if (tags.length > 1) {
// More tags than the one we're showing
// So we add a overflow element indicating there are more tags
const moreTagElement = renderTag('+' + (tags.length - 1), true)
moreTagElement.setAttribute('title', tags.slice(1).join(', '))
// because the title is not accessible we hide this element for screen readers (see alternative below)
moreTagElement.setAttribute('aria-hidden', 'true')
moreTagElement.setAttribute('role', 'presentation')
systemTagsElement.append(moreTagElement)
// For accessibility the tags are listed, as the title is not accessible
// but those tags are visually hidden
for (const tag of tags.slice(1)) {
const tagElement = renderTag(tag)
tagElement.classList.add('hidden-visually')
systemTagsElement.append(tagElement)
}
}
}
systemTagsElement.append(renderTag(tags[0]!))
if (tags.length === 2) {
// Special case only two tags:
// the overflow fake tag would take the same space as this, so render it
systemTagsElement.append(renderTag(tags[1]!))
} else if (tags.length > 1) {
// More tags than the one we're showing
// So we add a overflow element indicating there are more tags
const moreTagElement = renderTag('+' + (tags.length - 1), true)
moreTagElement.setAttribute('title', tags.slice(1).join(', '))
// because the title is not accessible we hide this element for screen readers (see alternative below)
moreTagElement.setAttribute('aria-hidden', 'true')
moreTagElement.setAttribute('role', 'presentation')
systemTagsElement.append(moreTagElement)
// For accessibility the tags are listed, as the title is not accessible
// but those tags are visually hidden
for (const tag of tags.slice(1)) {
const tagElement = renderTag(tag)
tagElement.classList.add('hidden-visually')
systemTagsElement.append(tagElement)
}
}
return systemTagsElement
return systemTagsElementWrapper
}
+6 -6
View File
@@ -130,7 +130,7 @@ OC.L10N.register(
"When logging in, {instanceName} will find the user based on the following attributes:" : "Bejelentkezéskor a(z) {instanceName} a következő attribútumok alapján találja meg a felhasználót:",
"Allows login against the LDAP/AD username, which is either 'uid' or 'sAMAccountName' and will be detected." : "Engedélyezi a bejelentkezést az LDAP/AD felhasználónév alapján ez vagy az „uid”, vagy az „sAMAccountName” mező, és észlelve lesz.",
"LDAP/AD Username:" : "LDAP/AD felhasználónév:",
"Allows login against an email attribute. 'mail' and 'mailPrimaryAddress' allowed." : "Engedélyezi a bejelentkezést egy email attribútum alapján. 'mail' és 'mailPrimaryAddress' megengedett.",
"Allows login against an email attribute. 'mail' and 'mailPrimaryAddress' allowed." : "Engedélyezi a bejelentkezést egy e-mail-attribútum alapján. A „mail és a „mailPrimaryAddress” engedélyezett.",
"LDAP/AD Email Address:" : "LDAP/AD e-mail-cím:",
"Other Attributes:" : "Más attribútumok:",
"Defines the filter to apply, when login is attempted. `%%uid` replaces the username in the login action. Example: `uid=%%uid`" : "Meghatározza a belépéskor alkalmazandó szűrőt. A `%%uid` lecseréli a felhasználónevet a bejelentkezési műveletnél. Például: `uid=%%uid`",
@@ -141,7 +141,7 @@ OC.L10N.register(
"More than 1,000 directory entries available." : "Több mint 1000 címtárbejegyzés érhető el.",
"_{ldapTestBase} entry available within the provided Base DN_::_{ldapTestBase} entries available within the provided Base DN_" : ["{ldapTestBase} bejegyzés érhető el a megadott alap DN alatt","{ldapTestBase} bejegyzés érhető el a megadott alap DN alatt"],
"When unchecked, this configuration will be skipped." : "Ha nincs kipipálva, ez a beállítás ki lesz hagyva.",
"Configuration active" : "Konfiguráció aktív",
"Configuration active" : "A beállítás aktív",
"Copy current configuration into new directory binding" : "Jelenlegi beállítások másolása egy új címtárkötésbe",
"Copy configuration" : "Konfiguráció másolása",
"Delete configuration" : "Konfiguráció törlése",
@@ -149,11 +149,11 @@ OC.L10N.register(
"Host" : "Kiszolgáló",
"Port" : "Port",
"Detect Port" : "Port észlelése",
"The DN of the client user with which the bind shall be done. For anonymous access, leave DN and Password empty." : "A kliens felhasználó DN-je, amellyel az összeköttetést kívánja. Anonim hozzáféréshez hagyja a DN-t és a jelszavat üresen.",
"The DN of the client user with which the bind shall be done. For anonymous access, leave DN and Password empty." : "A kliensfelhasználó DN-je, amely nevében a kötés történik. Az anonim eléréshez hagyja üresen a DN-t és a jelszót.",
"User DN" : "Alap DN",
"For anonymous access, leave DN and Password empty." : "Az anonim eléréshez hagyja üresen a DN és Jelszó mezőket.",
"Password" : "Jelszó",
"Save credentials" : "Felhasználóadatok mentése",
"Save credentials" : "Hitelesítő adatok mentése",
"Base DN" : "Alap DN",
"One Base DN per line" : "Soronként egy alap DN",
"You can specify Base DN for users and groups in the Advanced tab" : "A felhasználók és csoportok alap DN-jét a Speciális lapon adhatja meg",
@@ -180,14 +180,14 @@ OC.L10N.register(
"Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "A felhasználónevek a metaadatok kezeléséhez és tárolásához vannak felhasználva. Annak érdekében, hogy teljes mértékben azonosítható legyen egy felhasználó, minden LDAP felhasználó kapni fog egy belső felhasználónevet. Ez egy hozzárendelést igényel az eredeti felhasználónév és az LDAP fiók között. A létrejött felhasználónév hozzárendelődik az LDAP fiók UUID értékéhez. Emellett a DN gyorsítótárazott, hogy csökkentse az LDAP interakciók számát, de nincs használva azonosítás céljából. Ha a DN megváltozik, a rendszer észleli ezeket a változásokat. A belső felhasználónév van mindenhol használva a rendszeren belül. A hozzárendelések törlése adattöredékeket hagy maga után. A hozzárendelések ürítése nem beállításfüggő, minden LDAP beállításra hatással van. Soha ne ürítse éles rendszeren a hozzárendeléseket, csak tesztelési vagy kísérleti szakaszban.",
"Clear Username-LDAP User Mapping" : "FelhasználónévLDAP felhasználó hozzárendelés törlése",
"Clear Groupname-LDAP Group Mapping" : "CsoportLDAP csoport hozzárendelés törlése",
"Please renew your password" : "Kérjük, újítsa meg jelszavát",
"Please renew your password" : "Újítsa meg a jelszavát",
"An internal error occurred." : "Belső hiba történt.",
"Please try again or contact your administrator." : "Próbálja meg újra, vagy lépjen kapcsolatba a rendszergazdával.",
"Wrong password." : "Hibás jelszó.",
"Current password" : "Jelenlegi jelszó",
"New password" : "Új jelszó",
"Cancel" : "Mégse",
"Renewing…" : "Megújítás folyamatban...",
"Renewing…" : "Megújítás",
"Renew password" : "Jelszó megújítása",
"Confirm action" : "Művelet megerősítése",
"Are you sure you want to permanently delete this LDAP configuration? This cannot be undone." : "Biztos, hogy végleg törli ezt az LDAP konfigurációt? Ez nem vonható vissza.",
+6 -6
View File
@@ -128,7 +128,7 @@
"When logging in, {instanceName} will find the user based on the following attributes:" : "Bejelentkezéskor a(z) {instanceName} a következő attribútumok alapján találja meg a felhasználót:",
"Allows login against the LDAP/AD username, which is either 'uid' or 'sAMAccountName' and will be detected." : "Engedélyezi a bejelentkezést az LDAP/AD felhasználónév alapján ez vagy az „uid”, vagy az „sAMAccountName” mező, és észlelve lesz.",
"LDAP/AD Username:" : "LDAP/AD felhasználónév:",
"Allows login against an email attribute. 'mail' and 'mailPrimaryAddress' allowed." : "Engedélyezi a bejelentkezést egy email attribútum alapján. 'mail' és 'mailPrimaryAddress' megengedett.",
"Allows login against an email attribute. 'mail' and 'mailPrimaryAddress' allowed." : "Engedélyezi a bejelentkezést egy e-mail-attribútum alapján. A „mail és a „mailPrimaryAddress” engedélyezett.",
"LDAP/AD Email Address:" : "LDAP/AD e-mail-cím:",
"Other Attributes:" : "Más attribútumok:",
"Defines the filter to apply, when login is attempted. `%%uid` replaces the username in the login action. Example: `uid=%%uid`" : "Meghatározza a belépéskor alkalmazandó szűrőt. A `%%uid` lecseréli a felhasználónevet a bejelentkezési műveletnél. Például: `uid=%%uid`",
@@ -139,7 +139,7 @@
"More than 1,000 directory entries available." : "Több mint 1000 címtárbejegyzés érhető el.",
"_{ldapTestBase} entry available within the provided Base DN_::_{ldapTestBase} entries available within the provided Base DN_" : ["{ldapTestBase} bejegyzés érhető el a megadott alap DN alatt","{ldapTestBase} bejegyzés érhető el a megadott alap DN alatt"],
"When unchecked, this configuration will be skipped." : "Ha nincs kipipálva, ez a beállítás ki lesz hagyva.",
"Configuration active" : "Konfiguráció aktív",
"Configuration active" : "A beállítás aktív",
"Copy current configuration into new directory binding" : "Jelenlegi beállítások másolása egy új címtárkötésbe",
"Copy configuration" : "Konfiguráció másolása",
"Delete configuration" : "Konfiguráció törlése",
@@ -147,11 +147,11 @@
"Host" : "Kiszolgáló",
"Port" : "Port",
"Detect Port" : "Port észlelése",
"The DN of the client user with which the bind shall be done. For anonymous access, leave DN and Password empty." : "A kliens felhasználó DN-je, amellyel az összeköttetést kívánja. Anonim hozzáféréshez hagyja a DN-t és a jelszavat üresen.",
"The DN of the client user with which the bind shall be done. For anonymous access, leave DN and Password empty." : "A kliensfelhasználó DN-je, amely nevében a kötés történik. Az anonim eléréshez hagyja üresen a DN-t és a jelszót.",
"User DN" : "Alap DN",
"For anonymous access, leave DN and Password empty." : "Az anonim eléréshez hagyja üresen a DN és Jelszó mezőket.",
"Password" : "Jelszó",
"Save credentials" : "Felhasználóadatok mentése",
"Save credentials" : "Hitelesítő adatok mentése",
"Base DN" : "Alap DN",
"One Base DN per line" : "Soronként egy alap DN",
"You can specify Base DN for users and groups in the Advanced tab" : "A felhasználók és csoportok alap DN-jét a Speciális lapon adhatja meg",
@@ -178,14 +178,14 @@
"Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "A felhasználónevek a metaadatok kezeléséhez és tárolásához vannak felhasználva. Annak érdekében, hogy teljes mértékben azonosítható legyen egy felhasználó, minden LDAP felhasználó kapni fog egy belső felhasználónevet. Ez egy hozzárendelést igényel az eredeti felhasználónév és az LDAP fiók között. A létrejött felhasználónév hozzárendelődik az LDAP fiók UUID értékéhez. Emellett a DN gyorsítótárazott, hogy csökkentse az LDAP interakciók számát, de nincs használva azonosítás céljából. Ha a DN megváltozik, a rendszer észleli ezeket a változásokat. A belső felhasználónév van mindenhol használva a rendszeren belül. A hozzárendelések törlése adattöredékeket hagy maga után. A hozzárendelések ürítése nem beállításfüggő, minden LDAP beállításra hatással van. Soha ne ürítse éles rendszeren a hozzárendeléseket, csak tesztelési vagy kísérleti szakaszban.",
"Clear Username-LDAP User Mapping" : "FelhasználónévLDAP felhasználó hozzárendelés törlése",
"Clear Groupname-LDAP Group Mapping" : "CsoportLDAP csoport hozzárendelés törlése",
"Please renew your password" : "Kérjük, újítsa meg jelszavát",
"Please renew your password" : "Újítsa meg a jelszavát",
"An internal error occurred." : "Belső hiba történt.",
"Please try again or contact your administrator." : "Próbálja meg újra, vagy lépjen kapcsolatba a rendszergazdával.",
"Wrong password." : "Hibás jelszó.",
"Current password" : "Jelenlegi jelszó",
"New password" : "Új jelszó",
"Cancel" : "Mégse",
"Renewing…" : "Megújítás folyamatban...",
"Renewing…" : "Megújítás",
"Renew password" : "Jelszó megújítása",
"Confirm action" : "Művelet megerősítése",
"Are you sure you want to permanently delete this LDAP configuration? This cannot be undone." : "Biztos, hogy végleg törli ezt az LDAP konfigurációt? Ez nem vonható vissza.",
+1
View File
@@ -10,6 +10,7 @@ OC.L10N.register(
"Out of office" : "Ekki á staðnum",
"Working remotely" : "Fjarvinna",
"In a call" : "Er í símtali",
"Be right back" : "Kem innan skamms",
"User status" : "Staða notanda",
"Clear status after" : "Hreinsa stöðu eftir",
"Emoji for your status message" : "Tákn fyrir stöðufærsluna þína",
+1
View File
@@ -8,6 +8,7 @@
"Out of office" : "Ekki á staðnum",
"Working remotely" : "Fjarvinna",
"In a call" : "Er í símtali",
"Be right back" : "Kem innan skamms",
"User status" : "Staða notanda",
"Clear status after" : "Hreinsa stöðu eftir",
"Emoji for your status message" : "Tákn fyrir stöðufærsluna þína",

Some files were not shown because too many files have changed in this diff Show More