aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files')
-rw-r--r--apps/files/l10n/ar.js1
-rw-r--r--apps/files/l10n/ar.json1
-rw-r--r--apps/files/l10n/ca.js1
-rw-r--r--apps/files/l10n/ca.json1
-rw-r--r--apps/files/l10n/cs.js1
-rw-r--r--apps/files/l10n/cs.json1
-rw-r--r--apps/files/l10n/da.js1
-rw-r--r--apps/files/l10n/da.json1
-rw-r--r--apps/files/l10n/de.js1
-rw-r--r--apps/files/l10n/de.json1
-rw-r--r--apps/files/l10n/de_DE.js1
-rw-r--r--apps/files/l10n/de_DE.json1
-rw-r--r--apps/files/l10n/en_GB.js1
-rw-r--r--apps/files/l10n/en_GB.json1
-rw-r--r--apps/files/l10n/es.js1
-rw-r--r--apps/files/l10n/es.json1
-rw-r--r--apps/files/l10n/et_EE.js3
-rw-r--r--apps/files/l10n/et_EE.json3
-rw-r--r--apps/files/l10n/fr.js1
-rw-r--r--apps/files/l10n/fr.json1
-rw-r--r--apps/files/l10n/ga.js1
-rw-r--r--apps/files/l10n/ga.json1
-rw-r--r--apps/files/l10n/gl.js1
-rw-r--r--apps/files/l10n/gl.json1
-rw-r--r--apps/files/l10n/hu.js1
-rw-r--r--apps/files/l10n/hu.json1
-rw-r--r--apps/files/l10n/is.js1
-rw-r--r--apps/files/l10n/is.json1
-rw-r--r--apps/files/l10n/it.js1
-rw-r--r--apps/files/l10n/it.json1
-rw-r--r--apps/files/l10n/ja.js1
-rw-r--r--apps/files/l10n/ja.json1
-rw-r--r--apps/files/l10n/pl.js1
-rw-r--r--apps/files/l10n/pl.json1
-rw-r--r--apps/files/l10n/pt_BR.js1
-rw-r--r--apps/files/l10n/pt_BR.json1
-rw-r--r--apps/files/l10n/ru.js1
-rw-r--r--apps/files/l10n/ru.json1
-rw-r--r--apps/files/l10n/sk.js1
-rw-r--r--apps/files/l10n/sk.json1
-rw-r--r--apps/files/l10n/sr.js1
-rw-r--r--apps/files/l10n/sr.json1
-rw-r--r--apps/files/l10n/sv.js1
-rw-r--r--apps/files/l10n/sv.json1
-rw-r--r--apps/files/l10n/tr.js18
-rw-r--r--apps/files/l10n/tr.json18
-rw-r--r--apps/files/l10n/uk.js1
-rw-r--r--apps/files/l10n/uk.json1
-rw-r--r--apps/files/l10n/zh_CN.js1
-rw-r--r--apps/files/l10n/zh_CN.json1
-rw-r--r--apps/files/l10n/zh_HK.js1
-rw-r--r--apps/files/l10n/zh_HK.json1
-rw-r--r--apps/files/l10n/zh_TW.js1
-rw-r--r--apps/files/l10n/zh_TW.json1
-rw-r--r--apps/files/lib/Activity/FavoriteProvider.php1
-rw-r--r--apps/files/lib/Activity/Filter/Favorites.php1
-rw-r--r--apps/files/lib/Activity/Filter/FileChanges.php1
-rw-r--r--apps/files/lib/Activity/Provider.php1
-rw-r--r--apps/files/lib/Activity/Settings/FavoriteAction.php1
-rw-r--r--apps/files/lib/Activity/Settings/FileChanged.php1
-rw-r--r--apps/files/lib/Activity/Settings/FileFavoriteChanged.php1
-rw-r--r--apps/files/lib/Command/DeleteOrphanedFiles.php4
-rw-r--r--apps/files/lib/Command/SanitizeFilenames.php2
-rw-r--r--apps/files/lib/Command/Scan.php4
-rw-r--r--apps/files/lib/Command/ScanAppData.php1
-rw-r--r--apps/files/lib/Command/TransferOwnership.php24
-rw-r--r--apps/files/lib/Controller/ApiController.php7
-rw-r--r--apps/files/lib/Controller/DirectEditingController.php1
-rw-r--r--apps/files/lib/Controller/DirectEditingViewController.php1
-rw-r--r--apps/files/lib/Service/DirectEditingService.php1
-rw-r--r--apps/files/lib/Service/OwnershipTransferService.php93
-rw-r--r--apps/files/lib/Service/UserConfig.php1
-rw-r--r--apps/files/lib/Service/ViewConfig.php7
-rw-r--r--apps/files/src/actions/openInFilesAction.spec.ts2
-rw-r--r--apps/files/src/actions/openInFilesAction.ts18
-rw-r--r--apps/files/src/components/FilesNavigationItem.vue8
-rw-r--r--apps/files/src/components/FilesNavigationSearch.vue122
-rw-r--r--apps/files/src/composables/useBeforeNavigation.ts20
-rw-r--r--apps/files/src/composables/useFilenameFilter.ts47
-rw-r--r--apps/files/src/eventbus.d.ts12
-rw-r--r--apps/files/src/filters/FilenameFilter.ts18
-rw-r--r--apps/files/src/init.ts9
-rw-r--r--apps/files/src/router/router.ts27
-rw-r--r--apps/files/src/services/Search.spec.ts61
-rw-r--r--apps/files/src/services/Search.ts44
-rw-r--r--apps/files/src/services/WebDavSearch.ts83
-rw-r--r--apps/files/src/store/files.ts23
-rw-r--r--apps/files/src/store/search.ts170
-rw-r--r--apps/files/src/types.ts5
-rw-r--r--apps/files/src/views/FilesList.vue30
-rw-r--r--apps/files/src/views/Navigation.cy.ts25
-rw-r--r--apps/files/src/views/Navigation.vue16
-rw-r--r--apps/files/src/views/SearchEmptyView.vue57
-rw-r--r--apps/files/src/views/files.ts9
-rw-r--r--apps/files/src/views/search.ts51
-rw-r--r--apps/files/templates/index.php1
-rw-r--r--apps/files/tests/Controller/ApiControllerTest.php15
97 files changed, 921 insertions, 197 deletions
diff --git a/apps/files/l10n/ar.js b/apps/files/l10n/ar.js
index beb1e0eb1cc..ffd13adc0d5 100644
--- a/apps/files/l10n/ar.js
+++ b/apps/files/l10n/ar.js
@@ -224,6 +224,7 @@ OC.L10N.register(
"Show those shortcuts" : "أعرُض تلك الاختصارات",
"You" : "أنت",
"Shared multiple times with different people" : "تمّت مشاركته عدة مرات مع أشخاص متعددين",
+ "Unable to change the favorite state of the file" : "تعذّر تغيير حالة المُفضَّلة للملف",
"Error while loading the file data" : "خطأ اثناء تحميل بيانات الملف",
"Owner" : "المالك",
"Remove from favorites" : "إزالة من المفضلة",
diff --git a/apps/files/l10n/ar.json b/apps/files/l10n/ar.json
index b7872f2405d..19dc6eaad41 100644
--- a/apps/files/l10n/ar.json
+++ b/apps/files/l10n/ar.json
@@ -222,6 +222,7 @@
"Show those shortcuts" : "أعرُض تلك الاختصارات",
"You" : "أنت",
"Shared multiple times with different people" : "تمّت مشاركته عدة مرات مع أشخاص متعددين",
+ "Unable to change the favorite state of the file" : "تعذّر تغيير حالة المُفضَّلة للملف",
"Error while loading the file data" : "خطأ اثناء تحميل بيانات الملف",
"Owner" : "المالك",
"Remove from favorites" : "إزالة من المفضلة",
diff --git a/apps/files/l10n/ca.js b/apps/files/l10n/ca.js
index cc3cff9aac8..c087434a9d2 100644
--- a/apps/files/l10n/ca.js
+++ b/apps/files/l10n/ca.js
@@ -218,6 +218,7 @@ OC.L10N.register(
"Show those shortcuts" : "Mostra aquestes dreceres",
"You" : "Vós",
"Shared multiple times with different people" : "S'ha compartit diverses vegades amb persones diferents",
+ "Unable to change the favorite state of the file" : "No es pot canviar l'estat preferit del fitxer",
"Error while loading the file data" : "S'ha produït un error en carregar la informació del fitxer",
"Owner" : "Propietat",
"Remove from favorites" : "Suprimeix dels preferits",
diff --git a/apps/files/l10n/ca.json b/apps/files/l10n/ca.json
index e0304838a3e..1990d885d0b 100644
--- a/apps/files/l10n/ca.json
+++ b/apps/files/l10n/ca.json
@@ -216,6 +216,7 @@
"Show those shortcuts" : "Mostra aquestes dreceres",
"You" : "Vós",
"Shared multiple times with different people" : "S'ha compartit diverses vegades amb persones diferents",
+ "Unable to change the favorite state of the file" : "No es pot canviar l'estat preferit del fitxer",
"Error while loading the file data" : "S'ha produït un error en carregar la informació del fitxer",
"Owner" : "Propietat",
"Remove from favorites" : "Suprimeix dels preferits",
diff --git a/apps/files/l10n/cs.js b/apps/files/l10n/cs.js
index 8c82398bd13..43010512ed6 100644
--- a/apps/files/l10n/cs.js
+++ b/apps/files/l10n/cs.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Zobrazit tyto zkratky",
"You" : "Vy",
"Shared multiple times with different people" : "Nasdílet několikrát různým lidem",
+ "Unable to change the favorite state of the file" : "Nebylo možné změnit stav zařazení mezi oblíbené souboru",
"Error while loading the file data" : "Chyba při načítání dat souboru",
"Owner" : "Vlastník",
"Remove from favorites" : "Odebrat z oblíbených",
diff --git a/apps/files/l10n/cs.json b/apps/files/l10n/cs.json
index 5c5eb759467..20cfeaa25a0 100644
--- a/apps/files/l10n/cs.json
+++ b/apps/files/l10n/cs.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Zobrazit tyto zkratky",
"You" : "Vy",
"Shared multiple times with different people" : "Nasdílet několikrát různým lidem",
+ "Unable to change the favorite state of the file" : "Nebylo možné změnit stav zařazení mezi oblíbené souboru",
"Error while loading the file data" : "Chyba při načítání dat souboru",
"Owner" : "Vlastník",
"Remove from favorites" : "Odebrat z oblíbených",
diff --git a/apps/files/l10n/da.js b/apps/files/l10n/da.js
index 9dbfb1d372a..4a538d91e3f 100644
--- a/apps/files/l10n/da.js
+++ b/apps/files/l10n/da.js
@@ -218,6 +218,7 @@ OC.L10N.register(
"Show those shortcuts" : "Vis disse genveje",
"You" : "Dig",
"Shared multiple times with different people" : "Delt flere gange med forskellige mennesker",
+ "Unable to change the favorite state of the file" : "Kan ikke ændre favoritstatus for filen",
"Error while loading the file data" : "Fejl under indlæsning af fildata",
"Owner" : "Ejer",
"Remove from favorites" : "Fjern fra favoritter",
diff --git a/apps/files/l10n/da.json b/apps/files/l10n/da.json
index 0aa2c1be278..95cd920e0a0 100644
--- a/apps/files/l10n/da.json
+++ b/apps/files/l10n/da.json
@@ -216,6 +216,7 @@
"Show those shortcuts" : "Vis disse genveje",
"You" : "Dig",
"Shared multiple times with different people" : "Delt flere gange med forskellige mennesker",
+ "Unable to change the favorite state of the file" : "Kan ikke ændre favoritstatus for filen",
"Error while loading the file data" : "Fejl under indlæsning af fildata",
"Owner" : "Ejer",
"Remove from favorites" : "Fjern fra favoritter",
diff --git a/apps/files/l10n/de.js b/apps/files/l10n/de.js
index 599ce782ec8..80d45307fee 100644
--- a/apps/files/l10n/de.js
+++ b/apps/files/l10n/de.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Diese Tastaturkürzel anzeigen",
"You" : "Du",
"Shared multiple times with different people" : "Mehrmals mit verschiedenen Personen geteilt",
+ "Unable to change the favorite state of the file" : "Der Favoriten-Status der Datei kann nicht geändert werden",
"Error while loading the file data" : "Fehler beim Laden der Datei-Daten",
"Owner" : "Besitzer",
"Remove from favorites" : "Aus Favoriten entfernen",
diff --git a/apps/files/l10n/de.json b/apps/files/l10n/de.json
index f131177909e..571b3a02f62 100644
--- a/apps/files/l10n/de.json
+++ b/apps/files/l10n/de.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Diese Tastaturkürzel anzeigen",
"You" : "Du",
"Shared multiple times with different people" : "Mehrmals mit verschiedenen Personen geteilt",
+ "Unable to change the favorite state of the file" : "Der Favoriten-Status der Datei kann nicht geändert werden",
"Error while loading the file data" : "Fehler beim Laden der Datei-Daten",
"Owner" : "Besitzer",
"Remove from favorites" : "Aus Favoriten entfernen",
diff --git a/apps/files/l10n/de_DE.js b/apps/files/l10n/de_DE.js
index 0fb3fd77045..2528fa26903 100644
--- a/apps/files/l10n/de_DE.js
+++ b/apps/files/l10n/de_DE.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Diese Tastaturkürzel anzeigen",
"You" : "Sie",
"Shared multiple times with different people" : "Mehrmals mit verschiedenen Personen geteilt",
+ "Unable to change the favorite state of the file" : "Der Favoriten-Status der Datei kann nicht geändert werden",
"Error while loading the file data" : "Fehler beim Laden der Datei-Daten",
"Owner" : "Besitzer",
"Remove from favorites" : "Von Favoriten entfernen",
diff --git a/apps/files/l10n/de_DE.json b/apps/files/l10n/de_DE.json
index 3169b3751c1..2a661823839 100644
--- a/apps/files/l10n/de_DE.json
+++ b/apps/files/l10n/de_DE.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Diese Tastaturkürzel anzeigen",
"You" : "Sie",
"Shared multiple times with different people" : "Mehrmals mit verschiedenen Personen geteilt",
+ "Unable to change the favorite state of the file" : "Der Favoriten-Status der Datei kann nicht geändert werden",
"Error while loading the file data" : "Fehler beim Laden der Datei-Daten",
"Owner" : "Besitzer",
"Remove from favorites" : "Von Favoriten entfernen",
diff --git a/apps/files/l10n/en_GB.js b/apps/files/l10n/en_GB.js
index c5a7999dfc3..651479c0ba3 100644
--- a/apps/files/l10n/en_GB.js
+++ b/apps/files/l10n/en_GB.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Show those shortcuts",
"You" : "You",
"Shared multiple times with different people" : "Shared multiple times with different people",
+ "Unable to change the favorite state of the file" : "Unable to change the favorite state of the file",
"Error while loading the file data" : "Error while loading the file data",
"Owner" : "Owner",
"Remove from favorites" : "Remove from favourites",
diff --git a/apps/files/l10n/en_GB.json b/apps/files/l10n/en_GB.json
index 2178bba0961..c0be990b162 100644
--- a/apps/files/l10n/en_GB.json
+++ b/apps/files/l10n/en_GB.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Show those shortcuts",
"You" : "You",
"Shared multiple times with different people" : "Shared multiple times with different people",
+ "Unable to change the favorite state of the file" : "Unable to change the favorite state of the file",
"Error while loading the file data" : "Error while loading the file data",
"Owner" : "Owner",
"Remove from favorites" : "Remove from favourites",
diff --git a/apps/files/l10n/es.js b/apps/files/l10n/es.js
index 9b294b47eb9..f719cf94b8f 100644
--- a/apps/files/l10n/es.js
+++ b/apps/files/l10n/es.js
@@ -220,6 +220,7 @@ OC.L10N.register(
"Show those shortcuts" : "Mostrar estos atajos de teclado",
"You" : "Usted",
"Shared multiple times with different people" : "Compartido múltiples veces con diferentes personas",
+ "Unable to change the favorite state of the file" : "Deshacer los cambios del estado de favorito del archivo",
"Error while loading the file data" : "Error al cargar los datos del archivo",
"Owner" : "Dueño",
"Remove from favorites" : "Quitar de favoritos",
diff --git a/apps/files/l10n/es.json b/apps/files/l10n/es.json
index 75439fa538a..3d6910f16fd 100644
--- a/apps/files/l10n/es.json
+++ b/apps/files/l10n/es.json
@@ -218,6 +218,7 @@
"Show those shortcuts" : "Mostrar estos atajos de teclado",
"You" : "Usted",
"Shared multiple times with different people" : "Compartido múltiples veces con diferentes personas",
+ "Unable to change the favorite state of the file" : "Deshacer los cambios del estado de favorito del archivo",
"Error while loading the file data" : "Error al cargar los datos del archivo",
"Owner" : "Dueño",
"Remove from favorites" : "Quitar de favoritos",
diff --git a/apps/files/l10n/et_EE.js b/apps/files/l10n/et_EE.js
index 948cbb040ac..801501c12d3 100644
--- a/apps/files/l10n/et_EE.js
+++ b/apps/files/l10n/et_EE.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Näita neid otseteid",
"You" : "Sina",
"Shared multiple times with different people" : "Jagatud mitu korda eri kasutajate poolt",
+ "Unable to change the favorite state of the file" : "Faili olekut lemmikuna ei õnnestu muuta",
"Error while loading the file data" : "Viga faili andmete laadimisel",
"Owner" : "Omanik",
"Remove from favorites" : "Eemalda lemmikutest",
@@ -271,6 +272,8 @@ OC.L10N.register(
"Download" : "Laadi alla",
"Moving \"{source}\" to \"{destination}\" …" : "Teisaldan „{source}“ → „{destination}“…",
"Copying \"{source}\" to \"{destination}\" …" : "Kopeerin „{source}“ → „{destination}“…",
+ "Destination is not a folder" : "Sihtasukoht pole kaust",
+ "This file/folder is already in that directory" : "See fail või kaust juba asub selles kaustas",
"You cannot move a file/folder onto itself or into a subfolder of itself" : "Sa ei saa faili või kausta iseendaks teisaldada ega teisaldada kausta iseenda alamkausta",
"(copy)" : "(koopia)",
"(copy %n)" : "(%n koopia)",
diff --git a/apps/files/l10n/et_EE.json b/apps/files/l10n/et_EE.json
index f5bbc600470..16ec2e6a3fd 100644
--- a/apps/files/l10n/et_EE.json
+++ b/apps/files/l10n/et_EE.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Näita neid otseteid",
"You" : "Sina",
"Shared multiple times with different people" : "Jagatud mitu korda eri kasutajate poolt",
+ "Unable to change the favorite state of the file" : "Faili olekut lemmikuna ei õnnestu muuta",
"Error while loading the file data" : "Viga faili andmete laadimisel",
"Owner" : "Omanik",
"Remove from favorites" : "Eemalda lemmikutest",
@@ -269,6 +270,8 @@
"Download" : "Laadi alla",
"Moving \"{source}\" to \"{destination}\" …" : "Teisaldan „{source}“ → „{destination}“…",
"Copying \"{source}\" to \"{destination}\" …" : "Kopeerin „{source}“ → „{destination}“…",
+ "Destination is not a folder" : "Sihtasukoht pole kaust",
+ "This file/folder is already in that directory" : "See fail või kaust juba asub selles kaustas",
"You cannot move a file/folder onto itself or into a subfolder of itself" : "Sa ei saa faili või kausta iseendaks teisaldada ega teisaldada kausta iseenda alamkausta",
"(copy)" : "(koopia)",
"(copy %n)" : "(%n koopia)",
diff --git a/apps/files/l10n/fr.js b/apps/files/l10n/fr.js
index bd9c1a89b0e..aa856f2e618 100644
--- a/apps/files/l10n/fr.js
+++ b/apps/files/l10n/fr.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Montrer ces raccourcis",
"You" : "Vous",
"Shared multiple times with different people" : "Partagé plusieurs fois avec plusieurs personnes",
+ "Unable to change the favorite state of the file" : "Impossible de modifier l'état favori du fichier",
"Error while loading the file data" : "Erreur lors du chargement du fichier de données",
"Owner" : "Propriétaire",
"Remove from favorites" : "Retirer des favoris",
diff --git a/apps/files/l10n/fr.json b/apps/files/l10n/fr.json
index 2d3504a41f9..73ecb0659bb 100644
--- a/apps/files/l10n/fr.json
+++ b/apps/files/l10n/fr.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Montrer ces raccourcis",
"You" : "Vous",
"Shared multiple times with different people" : "Partagé plusieurs fois avec plusieurs personnes",
+ "Unable to change the favorite state of the file" : "Impossible de modifier l'état favori du fichier",
"Error while loading the file data" : "Erreur lors du chargement du fichier de données",
"Owner" : "Propriétaire",
"Remove from favorites" : "Retirer des favoris",
diff --git a/apps/files/l10n/ga.js b/apps/files/l10n/ga.js
index dd836588106..cc7185656dd 100644
--- a/apps/files/l10n/ga.js
+++ b/apps/files/l10n/ga.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Taispeáin na haicearraí sin",
"You" : "tú",
"Shared multiple times with different people" : "Roinnte go minic le daoine éagsúla",
+ "Unable to change the favorite state of the file" : "Ní féidir staid an chomhaid is fearr leat a athrú",
"Error while loading the file data" : "Earráid agus sonraí an chomhaid á lódáil",
"Owner" : "Úinéir",
"Remove from favorites" : "Bain ó cheanáin",
diff --git a/apps/files/l10n/ga.json b/apps/files/l10n/ga.json
index 52735440088..b68edfb56c7 100644
--- a/apps/files/l10n/ga.json
+++ b/apps/files/l10n/ga.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Taispeáin na haicearraí sin",
"You" : "tú",
"Shared multiple times with different people" : "Roinnte go minic le daoine éagsúla",
+ "Unable to change the favorite state of the file" : "Ní féidir staid an chomhaid is fearr leat a athrú",
"Error while loading the file data" : "Earráid agus sonraí an chomhaid á lódáil",
"Owner" : "Úinéir",
"Remove from favorites" : "Bain ó cheanáin",
diff --git a/apps/files/l10n/gl.js b/apps/files/l10n/gl.js
index 09641f25ab8..b27f426d550 100644
--- a/apps/files/l10n/gl.js
+++ b/apps/files/l10n/gl.js
@@ -219,6 +219,7 @@ OC.L10N.register(
"Show those shortcuts" : "Amosar eses atallos",
"You" : "Vde.",
"Shared multiple times with different people" : "Compartido varias veces con diferentes persoas",
+ "Unable to change the favorite state of the file" : "Non é posíbel cambiar o estado de favorito do ficheiro",
"Error while loading the file data" : "Produciuse un erro ao cargar os datos do ficheiro",
"Owner" : "Propietario",
"Remove from favorites" : "Retirar de favoritos",
diff --git a/apps/files/l10n/gl.json b/apps/files/l10n/gl.json
index 05b3ac5d05d..ff492c979ff 100644
--- a/apps/files/l10n/gl.json
+++ b/apps/files/l10n/gl.json
@@ -217,6 +217,7 @@
"Show those shortcuts" : "Amosar eses atallos",
"You" : "Vde.",
"Shared multiple times with different people" : "Compartido varias veces con diferentes persoas",
+ "Unable to change the favorite state of the file" : "Non é posíbel cambiar o estado de favorito do ficheiro",
"Error while loading the file data" : "Produciuse un erro ao cargar os datos do ficheiro",
"Owner" : "Propietario",
"Remove from favorites" : "Retirar de favoritos",
diff --git a/apps/files/l10n/hu.js b/apps/files/l10n/hu.js
index 49059d64de2..24e9c70a523 100644
--- a/apps/files/l10n/hu.js
+++ b/apps/files/l10n/hu.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Gyorsbillentyűk megjelenítése",
"You" : "Ön",
"Shared multiple times with different people" : "Többször megosztva különböző személyekkel",
+ "Unable to change the favorite state of the file" : "Nem lehet megváltoztatni a fájl kedvenc állapotát",
"Error while loading the file data" : "Hiba történt a fájladatok betöltése közben",
"Owner" : "Tulajdonos",
"Remove from favorites" : "Eltávolítás a kedvencekből",
diff --git a/apps/files/l10n/hu.json b/apps/files/l10n/hu.json
index 42501ffa19c..e291c309b34 100644
--- a/apps/files/l10n/hu.json
+++ b/apps/files/l10n/hu.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Gyorsbillentyűk megjelenítése",
"You" : "Ön",
"Shared multiple times with different people" : "Többször megosztva különböző személyekkel",
+ "Unable to change the favorite state of the file" : "Nem lehet megváltoztatni a fájl kedvenc állapotát",
"Error while loading the file data" : "Hiba történt a fájladatok betöltése közben",
"Owner" : "Tulajdonos",
"Remove from favorites" : "Eltávolítás a kedvencekből",
diff --git a/apps/files/l10n/is.js b/apps/files/l10n/is.js
index 550c0c9c4fc..744a4332569 100644
--- a/apps/files/l10n/is.js
+++ b/apps/files/l10n/is.js
@@ -206,6 +206,7 @@ OC.L10N.register(
"Show those shortcuts" : "Sýna þessa flýtilykla",
"You" : "Þú",
"Shared multiple times with different people" : "Deilt mörgum sinnum með mismunandi fólki",
+ "Unable to change the favorite state of the file" : "Get ekki breytt stöðu sem eftirlæti á skránni",
"Error while loading the file data" : "Villa við að hlaða inn skráagögnum",
"Owner" : "Eigandi",
"Remove from favorites" : "Fjarlægja úr eftirlætum",
diff --git a/apps/files/l10n/is.json b/apps/files/l10n/is.json
index e296f20ebb1..4a2722125c6 100644
--- a/apps/files/l10n/is.json
+++ b/apps/files/l10n/is.json
@@ -204,6 +204,7 @@
"Show those shortcuts" : "Sýna þessa flýtilykla",
"You" : "Þú",
"Shared multiple times with different people" : "Deilt mörgum sinnum með mismunandi fólki",
+ "Unable to change the favorite state of the file" : "Get ekki breytt stöðu sem eftirlæti á skránni",
"Error while loading the file data" : "Villa við að hlaða inn skráagögnum",
"Owner" : "Eigandi",
"Remove from favorites" : "Fjarlægja úr eftirlætum",
diff --git a/apps/files/l10n/it.js b/apps/files/l10n/it.js
index c0c1be3d760..614a38dfee8 100644
--- a/apps/files/l10n/it.js
+++ b/apps/files/l10n/it.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Mostra quelle scorciatoie",
"You" : "Tu",
"Shared multiple times with different people" : "Condiviso più volte con diverse persone",
+ "Unable to change the favorite state of the file" : "Impossibile modificare lo stato preferito del file",
"Error while loading the file data" : "Errore durante il caricamento del file di dati",
"Owner" : "Proprietario",
"Remove from favorites" : "Rimuovi dai preferiti",
diff --git a/apps/files/l10n/it.json b/apps/files/l10n/it.json
index e7450c4fcf7..46c1ad54125 100644
--- a/apps/files/l10n/it.json
+++ b/apps/files/l10n/it.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Mostra quelle scorciatoie",
"You" : "Tu",
"Shared multiple times with different people" : "Condiviso più volte con diverse persone",
+ "Unable to change the favorite state of the file" : "Impossibile modificare lo stato preferito del file",
"Error while loading the file data" : "Errore durante il caricamento del file di dati",
"Owner" : "Proprietario",
"Remove from favorites" : "Rimuovi dai preferiti",
diff --git a/apps/files/l10n/ja.js b/apps/files/l10n/ja.js
index c079fb21063..2ba264d20ea 100644
--- a/apps/files/l10n/ja.js
+++ b/apps/files/l10n/ja.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "これらのショートカットを表示する",
"You" : "自分",
"Shared multiple times with different people" : "異なる人と複数回共有",
+ "Unable to change the favorite state of the file" : "ファイルのお気に入りの状態を変更できません",
"Error while loading the file data" : "ファイルデータの読み込み中にエラーが発生しました",
"Owner" : "作成者",
"Remove from favorites" : "お気に入りから削除",
diff --git a/apps/files/l10n/ja.json b/apps/files/l10n/ja.json
index 67a1afa9371..ccdec912f64 100644
--- a/apps/files/l10n/ja.json
+++ b/apps/files/l10n/ja.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "これらのショートカットを表示する",
"You" : "自分",
"Shared multiple times with different people" : "異なる人と複数回共有",
+ "Unable to change the favorite state of the file" : "ファイルのお気に入りの状態を変更できません",
"Error while loading the file data" : "ファイルデータの読み込み中にエラーが発生しました",
"Owner" : "作成者",
"Remove from favorites" : "お気に入りから削除",
diff --git a/apps/files/l10n/pl.js b/apps/files/l10n/pl.js
index 0fbe930ae76..b10cdb2380f 100644
--- a/apps/files/l10n/pl.js
+++ b/apps/files/l10n/pl.js
@@ -227,6 +227,7 @@ OC.L10N.register(
"Show those shortcuts" : "Pokaż te skróty",
"You" : "Ty",
"Shared multiple times with different people" : "Udostępniony wiele razy różnym osobom",
+ "Unable to change the favorite state of the file" : "Nie można zmienić ulubionego stanu pliku",
"Error while loading the file data" : "Błąd podczas wczytywania danych pliku",
"Owner" : "Właściciel",
"Remove from favorites" : "Usuń z ulubionych",
diff --git a/apps/files/l10n/pl.json b/apps/files/l10n/pl.json
index 25dbcf89c01..f54507e7ff7 100644
--- a/apps/files/l10n/pl.json
+++ b/apps/files/l10n/pl.json
@@ -225,6 +225,7 @@
"Show those shortcuts" : "Pokaż te skróty",
"You" : "Ty",
"Shared multiple times with different people" : "Udostępniony wiele razy różnym osobom",
+ "Unable to change the favorite state of the file" : "Nie można zmienić ulubionego stanu pliku",
"Error while loading the file data" : "Błąd podczas wczytywania danych pliku",
"Owner" : "Właściciel",
"Remove from favorites" : "Usuń z ulubionych",
diff --git a/apps/files/l10n/pt_BR.js b/apps/files/l10n/pt_BR.js
index f4b6ddd5c53..722c29d09bb 100644
--- a/apps/files/l10n/pt_BR.js
+++ b/apps/files/l10n/pt_BR.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Mostrar esses atalhos",
"You" : "Você",
"Shared multiple times with different people" : "Compartilhado várias vezes com pessoas diferentes",
+ "Unable to change the favorite state of the file" : "Impossível mudar estado de favorito do arquivo",
"Error while loading the file data" : "Erro ao carregar os dados do arquivo",
"Owner" : "Proprietário",
"Remove from favorites" : "Excluir dos favoritos",
diff --git a/apps/files/l10n/pt_BR.json b/apps/files/l10n/pt_BR.json
index fc695c4f21b..8f361e72cf8 100644
--- a/apps/files/l10n/pt_BR.json
+++ b/apps/files/l10n/pt_BR.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Mostrar esses atalhos",
"You" : "Você",
"Shared multiple times with different people" : "Compartilhado várias vezes com pessoas diferentes",
+ "Unable to change the favorite state of the file" : "Impossível mudar estado de favorito do arquivo",
"Error while loading the file data" : "Erro ao carregar os dados do arquivo",
"Owner" : "Proprietário",
"Remove from favorites" : "Excluir dos favoritos",
diff --git a/apps/files/l10n/ru.js b/apps/files/l10n/ru.js
index 7b4c7c05c47..4f6cf29409b 100644
--- a/apps/files/l10n/ru.js
+++ b/apps/files/l10n/ru.js
@@ -227,6 +227,7 @@ OC.L10N.register(
"Show those shortcuts" : "Показать ярлыки",
"You" : "Вы",
"Shared multiple times with different people" : "Делиться несколько раз с разными людьми",
+ "Unable to change the favorite state of the file" : "Невозможно изменить избранное состояние файла",
"Error while loading the file data" : "Не удалось загрузить данные файла",
"Owner" : "Владелец",
"Remove from favorites" : "Удалить из избранных",
diff --git a/apps/files/l10n/ru.json b/apps/files/l10n/ru.json
index 9b5bb313718..32c53285922 100644
--- a/apps/files/l10n/ru.json
+++ b/apps/files/l10n/ru.json
@@ -225,6 +225,7 @@
"Show those shortcuts" : "Показать ярлыки",
"You" : "Вы",
"Shared multiple times with different people" : "Делиться несколько раз с разными людьми",
+ "Unable to change the favorite state of the file" : "Невозможно изменить избранное состояние файла",
"Error while loading the file data" : "Не удалось загрузить данные файла",
"Owner" : "Владелец",
"Remove from favorites" : "Удалить из избранных",
diff --git a/apps/files/l10n/sk.js b/apps/files/l10n/sk.js
index abdd49b9678..bcb44beb3f3 100644
--- a/apps/files/l10n/sk.js
+++ b/apps/files/l10n/sk.js
@@ -219,6 +219,7 @@ OC.L10N.register(
"Show those shortcuts" : "Zobraziť klávesové skratky",
"You" : "Vy",
"Shared multiple times with different people" : "Zdieľané viackrát rôznymi ľuďmi",
+ "Unable to change the favorite state of the file" : "Nie je možné zmeniť stav „obľúbené“ súboru",
"Error while loading the file data" : "Chyba pri načítaní údajov súboru",
"Owner" : "Vlastník",
"Remove from favorites" : "Odstrániť z obľúbených",
diff --git a/apps/files/l10n/sk.json b/apps/files/l10n/sk.json
index 97ab5fb9073..d9d2a6212d4 100644
--- a/apps/files/l10n/sk.json
+++ b/apps/files/l10n/sk.json
@@ -217,6 +217,7 @@
"Show those shortcuts" : "Zobraziť klávesové skratky",
"You" : "Vy",
"Shared multiple times with different people" : "Zdieľané viackrát rôznymi ľuďmi",
+ "Unable to change the favorite state of the file" : "Nie je možné zmeniť stav „obľúbené“ súboru",
"Error while loading the file data" : "Chyba pri načítaní údajov súboru",
"Owner" : "Vlastník",
"Remove from favorites" : "Odstrániť z obľúbených",
diff --git a/apps/files/l10n/sr.js b/apps/files/l10n/sr.js
index 7437c33f38c..7c26a7c95bd 100644
--- a/apps/files/l10n/sr.js
+++ b/apps/files/l10n/sr.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Прикажи те пречице",
"You" : "Ви",
"Shared multiple times with different people" : "Дељено више пута са разним људима",
+ "Unable to change the favorite state of the file" : "Није успела измена стања омиљено за фајл",
"Error while loading the file data" : "Грешка при учитавању података фајла",
"Owner" : "Власник",
"Remove from favorites" : "Избаци из омиљених",
diff --git a/apps/files/l10n/sr.json b/apps/files/l10n/sr.json
index 1ae51f59dba..c262023b8f3 100644
--- a/apps/files/l10n/sr.json
+++ b/apps/files/l10n/sr.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Прикажи те пречице",
"You" : "Ви",
"Shared multiple times with different people" : "Дељено више пута са разним људима",
+ "Unable to change the favorite state of the file" : "Није успела измена стања омиљено за фајл",
"Error while loading the file data" : "Грешка при учитавању података фајла",
"Owner" : "Власник",
"Remove from favorites" : "Избаци из омиљених",
diff --git a/apps/files/l10n/sv.js b/apps/files/l10n/sv.js
index 229ae071fc7..27d6814448f 100644
--- a/apps/files/l10n/sv.js
+++ b/apps/files/l10n/sv.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Visa dessa genvägar",
"You" : "Du",
"Shared multiple times with different people" : "Delad flera gånger med olika personer",
+ "Unable to change the favorite state of the file" : "Kan inte ändra filens favoritstatus",
"Error while loading the file data" : "Fel vid inläsning av fildata",
"Owner" : "Ägare",
"Remove from favorites" : "Ta bort från favoriter",
diff --git a/apps/files/l10n/sv.json b/apps/files/l10n/sv.json
index 35788f19371..3944641dad5 100644
--- a/apps/files/l10n/sv.json
+++ b/apps/files/l10n/sv.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Visa dessa genvägar",
"You" : "Du",
"Shared multiple times with different people" : "Delad flera gånger med olika personer",
+ "Unable to change the favorite state of the file" : "Kan inte ändra filens favoritstatus",
"Error while loading the file data" : "Fel vid inläsning av fildata",
"Owner" : "Ägare",
"Remove from favorites" : "Ta bort från favoriter",
diff --git a/apps/files/l10n/tr.js b/apps/files/l10n/tr.js
index 968c8d71d14..829f9438f3b 100644
--- a/apps/files/l10n/tr.js
+++ b/apps/files/l10n/tr.js
@@ -70,7 +70,9 @@ OC.L10N.register(
"Transferred from %1$s on %2$s" : "%1$s üzerinden %2$s zamanında aktarıldı",
"Files compatibility" : "Dosyalar uygulaması uyumluluğu",
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Dosyaların tüm istemcilerle eşitlenebilmesi için dosya adlarının kısıtlanmasını sağlar. Varsayılan olarak POSIX içinde geçerli olan tüm dosya adlarına (Linux veya macOS) izin verilir.",
- "Enforce Windows compatibility" : "Windows uyumluluğu dayatılsın",
+ "After enabling the Windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Windows uyumlu dosya adları açıldıktan sonra, var olan dosyalar artık değiştirilemez. Ancak sahipleri tarafından geçerli yeni adlarla yeniden adlandırılabilir.",
+ "It is also possible to migrate files automatically after enabling this setting, please refer to the documentation about the occ command." : "Bu ayarı açtıktan sonra dosyaları otomatik olarak da aktarılabilir. Lütfen occ komutuyla ilgili belgelere bakın.",
+ "Enforce Windows compatibility" : "Windows uyumluluğu zorunlu kılınsın",
"This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity." : "Ayrılmış adlar veya özel karakterler kullanmak gibi Windows sistemlerinde geçerli olmayan dosya adlarını engeller. Ancak büyük/küçük harf duyarlılığı uyumluluğunu zorunlu kılmaz.",
"File Management" : "Dosya yönetimi",
"Home" : "Giriş",
@@ -95,6 +97,11 @@ OC.L10N.register(
"Renamed \"{oldName}\" to \"{newName}\"" : "\"{oldName}\" adı \"{newName}\" olarak değiştirildi",
"Rename file" : "Dosyayı yeniden adlandır",
"Folder" : "Klasör",
+ "Unknown file type" : "Dosya türü bilinmiyor",
+ "{ext} image" : "{ext} görsel",
+ "{ext} video" : "{ext} görüntü",
+ "{ext} audio" : "{ext} ses",
+ "{ext} text" : "{ext} metin",
"Pending" : "Bekliyor",
"Unknown date" : "Tarih bilinmiyor",
"Clear filter" : "Süzgeci temizle",
@@ -105,6 +112,7 @@ OC.L10N.register(
"Total rows summary" : "Toplam satır özeti",
"Toggle selection for all files and folders" : "Tüm dosyaları ve klasörleri seç/bırak",
"Name" : "Ad",
+ "File type" : "Dosya türü",
"Size" : "Boyut",
"\"{displayName}\" failed on some elements" : "\"{displayName}\" bazı bileşenlerde tamamlanamadı",
"\"{displayName}\" batch action executed successfully" : "\"{displayName}\" toplu işlemi tamamlandı",
@@ -183,6 +191,7 @@ OC.L10N.register(
"Sort favorites first" : "Sık kullanılanlar üstte sıralansın",
"Sort folders before files" : "Klasörler dosyaların üzerinde sıralansın",
"Show hidden files" : "Gizli dosyaları görüntüle",
+ "Show file type column" : "Dosya türü sütunu görüntülensin",
"Crop image previews" : "Görsel ön izlemeleri kırpılsın",
"Enable the grid view" : "Tablo görünümü kullanılsın",
"Enable folder tree" : "Klasör ağacını aç",
@@ -219,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Bu kısayolları görüntüler",
"You" : "Siz",
"Shared multiple times with different people" : "Farklı kişilerle birkaç kez paylaşılmış",
+ "Unable to change the favorite state of the file" : "Dosyanın sık kullanılan durumu değiştirilemedi",
"Error while loading the file data" : "Dosya verileri yüklenirken sorun çıktı",
"Owner" : "Sahibi",
"Remove from favorites" : "Sık kullanılanlardan kaldır",
@@ -334,6 +344,7 @@ OC.L10N.register(
"Unexpected error: {error}" : "Beklenmeyen bir sorun çıktı: {error}",
"_%n file_::_%n files_" : ["%n dosya","%n dosya"],
"_%n folder_::_%n folders_" : ["%n klasör","%n klasör"],
+ "_%n hidden_::_%n hidden_" : ["%n gizli","%n gizli"],
"Filename must not be empty." : "Dosya adı boş olamaz.",
"\"{char}\" is not allowed inside a filename." : "Dosya adlarında \"{char}\" kullanılamaz.",
"\"{segment}\" is a reserved name and not allowed for filenames." : "\"{segment}\" ayrılmış bir ad olduğundan dosya adlarında kullanılamaz.",
@@ -444,6 +455,9 @@ OC.L10N.register(
"{fileCount} files and {folderCount} folders" : "{fileCount} dosya ve {folderCount} klasör",
"Personal Files" : "Kişisel dosyalar",
"Text file" : "Metin dosyası",
- "New text file.txt" : "Yeni metin dosyası.txt"
+ "New text file.txt" : "Yeni metin dosyası.txt",
+ "%1$s (renamed)" : "%1$s (yeniden adlandırıldı)",
+ "renamed file" : "dosyayı yeniden adlandırdı",
+ "After enabling the windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Windows uyumlu dosya adları açıldıktan sonra, var olan dosyalar artık değiştirilemez. Ancak sahipleri tarafından geçerli yeni adlarla yeniden adlandırılabilir."
},
"nplurals=2; plural=(n > 1);");
diff --git a/apps/files/l10n/tr.json b/apps/files/l10n/tr.json
index ae5760a8a05..a83f1db0285 100644
--- a/apps/files/l10n/tr.json
+++ b/apps/files/l10n/tr.json
@@ -68,7 +68,9 @@
"Transferred from %1$s on %2$s" : "%1$s üzerinden %2$s zamanında aktarıldı",
"Files compatibility" : "Dosyalar uygulaması uyumluluğu",
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Dosyaların tüm istemcilerle eşitlenebilmesi için dosya adlarının kısıtlanmasını sağlar. Varsayılan olarak POSIX içinde geçerli olan tüm dosya adlarına (Linux veya macOS) izin verilir.",
- "Enforce Windows compatibility" : "Windows uyumluluğu dayatılsın",
+ "After enabling the Windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Windows uyumlu dosya adları açıldıktan sonra, var olan dosyalar artık değiştirilemez. Ancak sahipleri tarafından geçerli yeni adlarla yeniden adlandırılabilir.",
+ "It is also possible to migrate files automatically after enabling this setting, please refer to the documentation about the occ command." : "Bu ayarı açtıktan sonra dosyaları otomatik olarak da aktarılabilir. Lütfen occ komutuyla ilgili belgelere bakın.",
+ "Enforce Windows compatibility" : "Windows uyumluluğu zorunlu kılınsın",
"This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity." : "Ayrılmış adlar veya özel karakterler kullanmak gibi Windows sistemlerinde geçerli olmayan dosya adlarını engeller. Ancak büyük/küçük harf duyarlılığı uyumluluğunu zorunlu kılmaz.",
"File Management" : "Dosya yönetimi",
"Home" : "Giriş",
@@ -93,6 +95,11 @@
"Renamed \"{oldName}\" to \"{newName}\"" : "\"{oldName}\" adı \"{newName}\" olarak değiştirildi",
"Rename file" : "Dosyayı yeniden adlandır",
"Folder" : "Klasör",
+ "Unknown file type" : "Dosya türü bilinmiyor",
+ "{ext} image" : "{ext} görsel",
+ "{ext} video" : "{ext} görüntü",
+ "{ext} audio" : "{ext} ses",
+ "{ext} text" : "{ext} metin",
"Pending" : "Bekliyor",
"Unknown date" : "Tarih bilinmiyor",
"Clear filter" : "Süzgeci temizle",
@@ -103,6 +110,7 @@
"Total rows summary" : "Toplam satır özeti",
"Toggle selection for all files and folders" : "Tüm dosyaları ve klasörleri seç/bırak",
"Name" : "Ad",
+ "File type" : "Dosya türü",
"Size" : "Boyut",
"\"{displayName}\" failed on some elements" : "\"{displayName}\" bazı bileşenlerde tamamlanamadı",
"\"{displayName}\" batch action executed successfully" : "\"{displayName}\" toplu işlemi tamamlandı",
@@ -181,6 +189,7 @@
"Sort favorites first" : "Sık kullanılanlar üstte sıralansın",
"Sort folders before files" : "Klasörler dosyaların üzerinde sıralansın",
"Show hidden files" : "Gizli dosyaları görüntüle",
+ "Show file type column" : "Dosya türü sütunu görüntülensin",
"Crop image previews" : "Görsel ön izlemeleri kırpılsın",
"Enable the grid view" : "Tablo görünümü kullanılsın",
"Enable folder tree" : "Klasör ağacını aç",
@@ -217,6 +226,7 @@
"Show those shortcuts" : "Bu kısayolları görüntüler",
"You" : "Siz",
"Shared multiple times with different people" : "Farklı kişilerle birkaç kez paylaşılmış",
+ "Unable to change the favorite state of the file" : "Dosyanın sık kullanılan durumu değiştirilemedi",
"Error while loading the file data" : "Dosya verileri yüklenirken sorun çıktı",
"Owner" : "Sahibi",
"Remove from favorites" : "Sık kullanılanlardan kaldır",
@@ -332,6 +342,7 @@
"Unexpected error: {error}" : "Beklenmeyen bir sorun çıktı: {error}",
"_%n file_::_%n files_" : ["%n dosya","%n dosya"],
"_%n folder_::_%n folders_" : ["%n klasör","%n klasör"],
+ "_%n hidden_::_%n hidden_" : ["%n gizli","%n gizli"],
"Filename must not be empty." : "Dosya adı boş olamaz.",
"\"{char}\" is not allowed inside a filename." : "Dosya adlarında \"{char}\" kullanılamaz.",
"\"{segment}\" is a reserved name and not allowed for filenames." : "\"{segment}\" ayrılmış bir ad olduğundan dosya adlarında kullanılamaz.",
@@ -442,6 +453,9 @@
"{fileCount} files and {folderCount} folders" : "{fileCount} dosya ve {folderCount} klasör",
"Personal Files" : "Kişisel dosyalar",
"Text file" : "Metin dosyası",
- "New text file.txt" : "Yeni metin dosyası.txt"
+ "New text file.txt" : "Yeni metin dosyası.txt",
+ "%1$s (renamed)" : "%1$s (yeniden adlandırıldı)",
+ "renamed file" : "dosyayı yeniden adlandırdı",
+ "After enabling the windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Windows uyumlu dosya adları açıldıktan sonra, var olan dosyalar artık değiştirilemez. Ancak sahipleri tarafından geçerli yeni adlarla yeniden adlandırılabilir."
},"pluralForm" :"nplurals=2; plural=(n > 1);"
} \ No newline at end of file
diff --git a/apps/files/l10n/uk.js b/apps/files/l10n/uk.js
index c82eabc2132..e1059df3ed0 100644
--- a/apps/files/l10n/uk.js
+++ b/apps/files/l10n/uk.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "Показувати ці скорочення",
"You" : "Ви",
"Shared multiple times with different people" : "Поділилися кілька разів з різними людьми",
+ "Unable to change the favorite state of the file" : "Неможливо змінити статус вподобаного файлу",
"Error while loading the file data" : "Помилка під час отримання даних щодо файлу",
"Owner" : "Власник",
"Remove from favorites" : "Прибрати зірочку",
diff --git a/apps/files/l10n/uk.json b/apps/files/l10n/uk.json
index 722583480c3..a0ce459f192 100644
--- a/apps/files/l10n/uk.json
+++ b/apps/files/l10n/uk.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "Показувати ці скорочення",
"You" : "Ви",
"Shared multiple times with different people" : "Поділилися кілька разів з різними людьми",
+ "Unable to change the favorite state of the file" : "Неможливо змінити статус вподобаного файлу",
"Error while loading the file data" : "Помилка під час отримання даних щодо файлу",
"Owner" : "Власник",
"Remove from favorites" : "Прибрати зірочку",
diff --git a/apps/files/l10n/zh_CN.js b/apps/files/l10n/zh_CN.js
index fc8bfeb65a9..1309bf4379f 100644
--- a/apps/files/l10n/zh_CN.js
+++ b/apps/files/l10n/zh_CN.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "显示快捷键",
"You" : "您",
"Shared multiple times with different people" : "与不同的用户多次分享",
+ "Unable to change the favorite state of the file" : "无法更改文件的收藏状态",
"Error while loading the file data" : "加载文件数据时出错",
"Owner" : "拥有者",
"Remove from favorites" : "从收藏中移除",
diff --git a/apps/files/l10n/zh_CN.json b/apps/files/l10n/zh_CN.json
index 2adb4da98d8..aca369c6619 100644
--- a/apps/files/l10n/zh_CN.json
+++ b/apps/files/l10n/zh_CN.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "显示快捷键",
"You" : "您",
"Shared multiple times with different people" : "与不同的用户多次分享",
+ "Unable to change the favorite state of the file" : "无法更改文件的收藏状态",
"Error while loading the file data" : "加载文件数据时出错",
"Owner" : "拥有者",
"Remove from favorites" : "从收藏中移除",
diff --git a/apps/files/l10n/zh_HK.js b/apps/files/l10n/zh_HK.js
index 0fc87c590dc..12e62becfe7 100644
--- a/apps/files/l10n/zh_HK.js
+++ b/apps/files/l10n/zh_HK.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "顯示這些快捷鍵",
"You" : "您",
"Shared multiple times with different people" : "與不同的人多次分享",
+ "Unable to change the favorite state of the file" : "無法更改檔案的最愛狀態",
"Error while loading the file data" : "載入檔案資料失敗",
"Owner" : "擁有者",
"Remove from favorites" : "從最愛中移除",
diff --git a/apps/files/l10n/zh_HK.json b/apps/files/l10n/zh_HK.json
index 8c4e9b0b4cd..c211e9cf075 100644
--- a/apps/files/l10n/zh_HK.json
+++ b/apps/files/l10n/zh_HK.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "顯示這些快捷鍵",
"You" : "您",
"Shared multiple times with different people" : "與不同的人多次分享",
+ "Unable to change the favorite state of the file" : "無法更改檔案的最愛狀態",
"Error while loading the file data" : "載入檔案資料失敗",
"Owner" : "擁有者",
"Remove from favorites" : "從最愛中移除",
diff --git a/apps/files/l10n/zh_TW.js b/apps/files/l10n/zh_TW.js
index 46450967a8d..cfdfa03dc07 100644
--- a/apps/files/l10n/zh_TW.js
+++ b/apps/files/l10n/zh_TW.js
@@ -228,6 +228,7 @@ OC.L10N.register(
"Show those shortcuts" : "顯示這些快捷鍵",
"You" : "您",
"Shared multiple times with different people" : "與不同的人多次分享",
+ "Unable to change the favorite state of the file" : "無法變更檔案的喜愛狀態",
"Error while loading the file data" : "載入檔案資料時發生錯誤",
"Owner" : "擁有者",
"Remove from favorites" : "從喜愛中移除",
diff --git a/apps/files/l10n/zh_TW.json b/apps/files/l10n/zh_TW.json
index 4ba080ebd64..4af203b309e 100644
--- a/apps/files/l10n/zh_TW.json
+++ b/apps/files/l10n/zh_TW.json
@@ -226,6 +226,7 @@
"Show those shortcuts" : "顯示這些快捷鍵",
"You" : "您",
"Shared multiple times with different people" : "與不同的人多次分享",
+ "Unable to change the favorite state of the file" : "無法變更檔案的喜愛狀態",
"Error while loading the file data" : "載入檔案資料時發生錯誤",
"Owner" : "擁有者",
"Remove from favorites" : "從喜愛中移除",
diff --git a/apps/files/lib/Activity/FavoriteProvider.php b/apps/files/lib/Activity/FavoriteProvider.php
index 7a660287192..e56b13b902a 100644
--- a/apps/files/lib/Activity/FavoriteProvider.php
+++ b/apps/files/lib/Activity/FavoriteProvider.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Activity/Filter/Favorites.php b/apps/files/lib/Activity/Filter/Favorites.php
index 6683f0bc44e..0159dd20b82 100644
--- a/apps/files/lib/Activity/Filter/Favorites.php
+++ b/apps/files/lib/Activity/Filter/Favorites.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Activity/Filter/FileChanges.php b/apps/files/lib/Activity/Filter/FileChanges.php
index 215510147d6..0ca8f6792e0 100644
--- a/apps/files/lib/Activity/Filter/FileChanges.php
+++ b/apps/files/lib/Activity/Filter/FileChanges.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Activity/Provider.php b/apps/files/lib/Activity/Provider.php
index faa2bbd0b3b..3ef79ac107f 100644
--- a/apps/files/lib/Activity/Provider.php
+++ b/apps/files/lib/Activity/Provider.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Activity/Settings/FavoriteAction.php b/apps/files/lib/Activity/Settings/FavoriteAction.php
index b572a9546e0..73b200341ec 100644
--- a/apps/files/lib/Activity/Settings/FavoriteAction.php
+++ b/apps/files/lib/Activity/Settings/FavoriteAction.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Activity/Settings/FileChanged.php b/apps/files/lib/Activity/Settings/FileChanged.php
index 5af87456550..c33ed5e1eba 100644
--- a/apps/files/lib/Activity/Settings/FileChanged.php
+++ b/apps/files/lib/Activity/Settings/FileChanged.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Activity/Settings/FileFavoriteChanged.php b/apps/files/lib/Activity/Settings/FileFavoriteChanged.php
index e4ac572f3f3..5000902ed3f 100644
--- a/apps/files/lib/Activity/Settings/FileFavoriteChanged.php
+++ b/apps/files/lib/Activity/Settings/FileFavoriteChanged.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Command/DeleteOrphanedFiles.php b/apps/files/lib/Command/DeleteOrphanedFiles.php
index 0f3375d2e93..37cb3159f4a 100644
--- a/apps/files/lib/Command/DeleteOrphanedFiles.php
+++ b/apps/files/lib/Command/DeleteOrphanedFiles.php
@@ -54,7 +54,7 @@ class DeleteOrphanedFiles extends Command {
$deletedMounts = $this->cleanupOrphanedMounts();
$output->writeln("$deletedMounts orphaned mount entries deleted");
-
+
return self::SUCCESS;
}
@@ -112,7 +112,7 @@ class DeleteOrphanedFiles extends Command {
return $deletedEntries;
}
-
+
/**
* @param array<int, int[]> $fileIdsByStorage
* @return int
diff --git a/apps/files/lib/Command/SanitizeFilenames.php b/apps/files/lib/Command/SanitizeFilenames.php
index a06b820ee6c..88d41d1cb5e 100644
--- a/apps/files/lib/Command/SanitizeFilenames.php
+++ b/apps/files/lib/Command/SanitizeFilenames.php
@@ -62,7 +62,7 @@ class SanitizeFilenames extends Base {
mode: InputOption::VALUE_REQUIRED,
description: 'Replacement for invalid character (by default space, underscore or dash is used)',
);
-
+
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/apps/files/lib/Command/Scan.php b/apps/files/lib/Command/Scan.php
index bd99dd28e60..b9057139b0e 100644
--- a/apps/files/lib/Command/Scan.php
+++ b/apps/files/lib/Command/Scan.php
@@ -286,8 +286,8 @@ class Scan extends Base {
$this->execTime = -microtime(true);
// Convert PHP errors to exceptions
set_error_handler(
- fn (int $severity, string $message, string $file, int $line): bool =>
- $this->exceptionErrorHandler($output, $severity, $message, $file, $line),
+ fn (int $severity, string $message, string $file, int $line): bool
+ => $this->exceptionErrorHandler($output, $severity, $message, $file, $line),
E_ALL
);
}
diff --git a/apps/files/lib/Command/ScanAppData.php b/apps/files/lib/Command/ScanAppData.php
index 81c80cab373..0e08c6a8cfe 100644
--- a/apps/files/lib/Command/ScanAppData.php
+++ b/apps/files/lib/Command/ScanAppData.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php
index 104a8fb4985..f7663e26f28 100644
--- a/apps/files/lib/Command/TransferOwnership.php
+++ b/apps/files/lib/Command/TransferOwnership.php
@@ -64,7 +64,7 @@ class TransferOwnership extends Command {
'transfer-incoming-shares',
null,
InputOption::VALUE_OPTIONAL,
- 'transfer incoming user file shares to destination user. Usage: --transfer-incoming-shares=1 (value required)',
+ 'Incoming shares are always transferred now, so this option does not affect the ownership transfer anymore',
'2'
)->addOption(
'include-external-storage',
@@ -129,27 +129,6 @@ class TransferOwnership extends Command {
}
try {
- $includeIncomingArgument = $input->getOption('transfer-incoming-shares');
-
- switch ($includeIncomingArgument) {
- case '0':
- $includeIncoming = false;
- break;
- case '1':
- $includeIncoming = true;
- break;
- case '2':
- $includeIncoming = $this->config->getSystemValue('transferIncomingShares', false);
- if (gettype($includeIncoming) !== 'boolean') {
- $output->writeln("<error> config.php: 'transfer-incoming-shares': wrong usage. Transfer aborted.</error>");
- return self::FAILURE;
- }
- break;
- default:
- $output->writeln('<error>Option --transfer-incoming-shares: wrong usage. Transfer aborted.</error>');
- return self::FAILURE;
- }
-
$this->transferService->transfer(
$sourceUserObject,
$destinationUserObject,
@@ -157,7 +136,6 @@ class TransferOwnership extends Command {
$output,
$input->getOption('move') === true,
false,
- $includeIncoming,
$includeExternalStorage,
);
} catch (TransferOwnershipException $e) {
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
index 9c683b7f41f..8bb024fb698 100644
--- a/apps/files/lib/Controller/ApiController.php
+++ b/apps/files/lib/Controller/ApiController.php
@@ -105,11 +105,12 @@ class ApiController extends Controller {
}
// Validate the user is allowed to download the file (preview is some kind of download)
+ /** @var ISharedStorage $storage */
$storage = $file->getStorage();
if ($storage->instanceOfStorage(ISharedStorage::class)) {
- /** @var ISharedStorage $storage */
- $attributes = $storage->getShare()->getAttributes();
- if ($attributes !== null && $attributes->getAttribute('permissions', 'download') === false) {
+ /** @var IShare $share */
+ $share = $storage->getShare();
+ if (!$share->canSeeContent()) {
throw new NotFoundException();
}
}
diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php
index 2c910006df5..c8addc33e98 100644
--- a/apps/files/lib/Controller/DirectEditingController.php
+++ b/apps/files/lib/Controller/DirectEditingController.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Controller/DirectEditingViewController.php b/apps/files/lib/Controller/DirectEditingViewController.php
index 1d78e2af0e0..b13e68f7766 100644
--- a/apps/files/lib/Controller/DirectEditingViewController.php
+++ b/apps/files/lib/Controller/DirectEditingViewController.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Service/DirectEditingService.php b/apps/files/lib/Service/DirectEditingService.php
index 243ddebdc67..3d756ee56fa 100644
--- a/apps/files/lib/Service/DirectEditingService.php
+++ b/apps/files/lib/Service/DirectEditingService.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php
index b3a36ee13e5..84c99f32109 100644
--- a/apps/files/lib/Service/OwnershipTransferService.php
+++ b/apps/files/lib/Service/OwnershipTransferService.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\Files\Service;
use Closure;
+use Exception;
use OC\Files\Filesystem;
use OC\Files\View;
use OC\User\NoUserException;
@@ -19,6 +20,7 @@ use OCA\Files_External\Config\ConfigAdapter;
use OCP\Encryption\IManager as IEncryptionManager;
use OCP\Files\Config\IHomeMountProvider;
use OCP\Files\Config\IUserMountCache;
+use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\InvalidPathException;
use OCP\Files\IRootFolder;
@@ -70,7 +72,6 @@ class OwnershipTransferService {
?OutputInterface $output = null,
bool $move = false,
bool $firstLogin = false,
- bool $transferIncomingShares = false,
bool $includeExternalStorage = false,
): void {
$output = $output ?? new NullOutput();
@@ -157,29 +158,26 @@ class OwnershipTransferService {
$sizeDifference = $sourceSize - $view->getFileInfo($finalTarget)->getSize();
// transfer the incoming shares
- if ($transferIncomingShares === true) {
- $sourceShares = $this->collectIncomingShares(
- $sourceUid,
- $output,
- $view
- );
- $destinationShares = $this->collectIncomingShares(
- $destinationUid,
- $output,
- $view,
- true
- );
- $this->transferIncomingShares(
- $sourceUid,
- $destinationUid,
- $sourceShares,
- $destinationShares,
- $output,
- $path,
- $finalTarget,
- $move
- );
- }
+ $sourceShares = $this->collectIncomingShares(
+ $sourceUid,
+ $output,
+ $sourcePath,
+ );
+ $destinationShares = $this->collectIncomingShares(
+ $destinationUid,
+ $output,
+ null,
+ );
+ $this->transferIncomingShares(
+ $sourceUid,
+ $destinationUid,
+ $sourceShares,
+ $destinationShares,
+ $output,
+ $path,
+ $finalTarget,
+ $move
+ );
$destinationPath = $finalTarget . '/' . $path;
// restore the shares
@@ -258,8 +256,8 @@ class OwnershipTransferService {
$mount = $fileInfo->getMountPoint();
// only analyze into folders from main storage,
if (
- $mount->getMountProvider() instanceof IHomeMountProvider ||
- ($includeExternalStorage && $mount->getMountProvider() instanceof ConfigAdapter)
+ $mount->getMountProvider() instanceof IHomeMountProvider
+ || ($includeExternalStorage && $mount->getMountProvider() instanceof ConfigAdapter)
) {
if ($fileInfo->isEncrypted()) {
/* Encrypted folder means e2ee encrypted, we cannot transfer it */
@@ -344,7 +342,7 @@ class OwnershipTransferService {
return mb_strpos(
Filesystem::normalizePath($relativePath . '/', false),
$normalizedPath . '/') === 0;
- } catch (\Exception $e) {
+ } catch (Exception $e) {
return false;
}
});
@@ -372,14 +370,16 @@ class OwnershipTransferService {
}, $shares)));
}
- private function collectIncomingShares(string $sourceUid,
+ private function collectIncomingShares(
+ string $sourceUid,
OutputInterface $output,
- View $view,
- bool $addKeys = false): array {
+ ?string $path,
+ ): array {
$output->writeln("Collecting all incoming share information for files and folders of $sourceUid ...");
$shares = [];
$progress = new ProgressBar($output);
+ $normalizedPath = Filesystem::normalizePath($path);
$offset = 0;
while (true) {
@@ -388,14 +388,19 @@ class OwnershipTransferService {
if (empty($sharePage)) {
break;
}
- if ($addKeys) {
- foreach ($sharePage as $singleShare) {
- $shares[$singleShare->getNodeId()] = $singleShare;
- }
- } else {
- foreach ($sharePage as $singleShare) {
- $shares[] = $singleShare;
- }
+
+ if ($path !== null && $path !== "$sourceUid/files") {
+ $sharePage = array_filter($sharePage, static function (IShare $share) use ($sourceUid, $normalizedPath) {
+ try {
+ return str_starts_with(Filesystem::normalizePath($sourceUid . '/files' . $share->getTarget() . '/', false), $normalizedPath . '/');
+ } catch (Exception) {
+ return false;
+ }
+ });
+ }
+
+ foreach ($sharePage as $share) {
+ $shares[$share->getNodeId()] = $share;
}
$offset += 50;
@@ -487,8 +492,8 @@ class OwnershipTransferService {
foreach ($shares as ['share' => $share, 'suffix' => $suffix]) {
try {
$output->writeln('Transfering share ' . $share->getId() . ' of type ' . $share->getShareType(), OutputInterface::VERBOSITY_VERBOSE);
- if ($share->getShareType() === IShare::TYPE_USER &&
- $share->getSharedWith() === $destinationUid) {
+ if ($share->getShareType() === IShare::TYPE_USER
+ && $share->getSharedWith() === $destinationUid) {
// Unmount the shares before deleting, so we don't try to get the storage later on.
$shareMountPoint = $this->mountManager->find('/' . $destinationUid . '/files' . $share->getTarget());
if ($shareMountPoint) {
@@ -503,8 +508,8 @@ class OwnershipTransferService {
$share->setSharedBy($destinationUid);
}
- if ($share->getShareType() === IShare::TYPE_USER &&
- !$this->userManager->userExists($share->getSharedWith())) {
+ if ($share->getShareType() === IShare::TYPE_USER
+ && !$this->userManager->userExists($share->getSharedWith())) {
// stray share with deleted user
$output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted user "' . $share->getSharedWith() . '", deleting</error>');
$this->shareManager->deleteShare($share);
@@ -569,8 +574,8 @@ class OwnershipTransferService {
}
$shareTarget = $share->getTarget();
$shareTarget = $finalShareTarget . $shareTarget;
- if ($share->getShareType() === IShare::TYPE_USER &&
- $share->getSharedBy() === $destinationUid) {
+ if ($share->getShareType() === IShare::TYPE_USER
+ && $share->getSharedBy() === $destinationUid) {
$this->shareManager->deleteShare($share);
} elseif (isset($destinationShares[$share->getNodeId()])) {
$destinationShare = $destinationShares[$share->getNodeId()];
diff --git a/apps/files/lib/Service/UserConfig.php b/apps/files/lib/Service/UserConfig.php
index a2f2f1c1d14..dee89b990c6 100644
--- a/apps/files/lib/Service/UserConfig.php
+++ b/apps/files/lib/Service/UserConfig.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/lib/Service/ViewConfig.php b/apps/files/lib/Service/ViewConfig.php
index 348ce6596e5..cf8bebd5372 100644
--- a/apps/files/lib/Service/ViewConfig.php
+++ b/apps/files/lib/Service/ViewConfig.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -103,7 +104,7 @@ class ViewConfig {
if (!in_array($key, $this->getAllowedConfigKeys())) {
throw new \InvalidArgumentException('Unknown config key');
}
-
+
if (!in_array($value, $this->getAllowedConfigValues($key))
&& !empty($this->getAllowedConfigValues($key))) {
throw new \InvalidArgumentException('Invalid config value');
@@ -132,7 +133,7 @@ class ViewConfig {
$userId = $this->user->getUID();
$configs = json_decode($this->config->getUserValue($userId, Application::APP_ID, self::CONFIG_KEY, '[]'), true);
-
+
if (!isset($configs[$view])) {
$configs[$view] = [];
}
@@ -158,7 +159,7 @@ class ViewConfig {
$userId = $this->user->getUID();
$configs = json_decode($this->config->getUserValue($userId, Application::APP_ID, self::CONFIG_KEY, '[]'), true);
$views = array_keys($configs);
-
+
return array_reduce($views, function ($carry, $view) use ($configs) {
$carry[$view] = $this->getConfig($view);
return $carry;
diff --git a/apps/files/src/actions/openInFilesAction.spec.ts b/apps/files/src/actions/openInFilesAction.spec.ts
index e732270d4c0..3ccd15fa2d2 100644
--- a/apps/files/src/actions/openInFilesAction.spec.ts
+++ b/apps/files/src/actions/openInFilesAction.spec.ts
@@ -19,7 +19,7 @@ const recentView = {
describe('Open in files action conditions tests', () => {
test('Default values', () => {
expect(action).toBeInstanceOf(FileAction)
- expect(action.id).toBe('open-in-files-recent')
+ expect(action.id).toBe('open-in-files')
expect(action.displayName([], recentView)).toBe('Open in Files')
expect(action.iconSvgInline([], recentView)).toBe('')
expect(action.default).toBe(DefaultType.HIDDEN)
diff --git a/apps/files/src/actions/openInFilesAction.ts b/apps/files/src/actions/openInFilesAction.ts
index 10e19e7eace..9e10b1ac74e 100644
--- a/apps/files/src/actions/openInFilesAction.ts
+++ b/apps/files/src/actions/openInFilesAction.ts
@@ -2,19 +2,21 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import { translate as t } from '@nextcloud/l10n'
-import { type Node, FileType, FileAction, DefaultType } from '@nextcloud/files'
-/**
- * TODO: Move away from a redirect and handle
- * navigation straight out of the recent view
- */
+import type { Node } from '@nextcloud/files'
+
+import { t } from '@nextcloud/l10n'
+import { FileType, FileAction, DefaultType } from '@nextcloud/files'
+import { VIEW_ID as SEARCH_VIEW_ID } from '../views/search'
+
export const action = new FileAction({
- id: 'open-in-files-recent',
+ id: 'open-in-files',
displayName: () => t('files', 'Open in Files'),
iconSvgInline: () => '',
- enabled: (nodes, view) => view.id === 'recent',
+ enabled(nodes, view) {
+ return view.id === 'recent' || view.id === SEARCH_VIEW_ID
+ },
async exec(node: Node) {
let dir = node.dirname
diff --git a/apps/files/src/components/FilesNavigationItem.vue b/apps/files/src/components/FilesNavigationItem.vue
index 372a83e1441..2c7c8b4b944 100644
--- a/apps/files/src/components/FilesNavigationItem.vue
+++ b/apps/files/src/components/FilesNavigationItem.vue
@@ -89,7 +89,7 @@ export default defineComponent({
return (Object.values(this.views).reduce((acc, views) => [...acc, ...views], []) as View[])
.filter(view => view.params?.dir.startsWith(this.parent.params?.dir))
}
- return this.views[this.parent.id] ?? [] // Root level views have `undefined` parent ids
+ return this.filterVisible(this.views[this.parent.id] ?? [])
},
style() {
@@ -103,11 +103,15 @@ export default defineComponent({
},
methods: {
+ filterVisible(views: View[]) {
+ return views.filter(({ _view, id }) => id === this.currentView?.id || _view.hidden !== true)
+ },
+
hasChildViews(view: View): boolean {
if (this.level >= maxLevel) {
return false
}
- return this.views[view.id]?.length > 0
+ return this.filterVisible(this.views[view.id] ?? []).length > 0
},
/**
diff --git a/apps/files/src/components/FilesNavigationSearch.vue b/apps/files/src/components/FilesNavigationSearch.vue
new file mode 100644
index 00000000000..85dc5534e5e
--- /dev/null
+++ b/apps/files/src/components/FilesNavigationSearch.vue
@@ -0,0 +1,122 @@
+<!--
+ - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<script setup lang="ts">
+import { mdiMagnify, mdiSearchWeb } from '@mdi/js'
+import { t } from '@nextcloud/l10n'
+import { computed } from 'vue'
+import NcActions from '@nextcloud/vue/components/NcActions'
+import NcActionButton from '@nextcloud/vue/components/NcActionButton'
+import NcAppNavigationSearch from '@nextcloud/vue/components/NcAppNavigationSearch'
+import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
+import { onBeforeNavigation } from '../composables/useBeforeNavigation.ts'
+import { useNavigation } from '../composables/useNavigation.ts'
+import { useRouteParameters } from '../composables/useRouteParameters.ts'
+import { useFilesStore } from '../store/files.ts'
+import { useSearchStore } from '../store/search.ts'
+import { VIEW_ID } from '../views/search.ts'
+
+const { currentView } = useNavigation(true)
+const { directory } = useRouteParameters()
+
+const filesStore = useFilesStore()
+const searchStore = useSearchStore()
+
+/**
+ * When the route is changed from search view to something different
+ * we need to clear the search box.
+ */
+onBeforeNavigation((to, from, next) => {
+ if (to.params.view !== VIEW_ID && from.params.view === VIEW_ID) {
+ // we are leaving the search view so unset the query
+ searchStore.query = ''
+ searchStore.scope = 'filter'
+ } else if (to.params.view === VIEW_ID && from.params.view === VIEW_ID) {
+ // fix the query if the user refreshed the view
+ if (searchStore.query && !to.query.query) {
+ // @ts-expect-error This is a weird issue with vue-router v4 and will be fixed in v5 (vue 3)
+ return next({
+ ...to,
+ query: {
+ ...to.query,
+ query: searchStore.query,
+ },
+ })
+ }
+ }
+ next()
+})
+
+/**
+ * Are we currently on the search view.
+ * Needed to disable the action menu (we cannot change the search mode there)
+ */
+const isSearchView = computed(() => currentView.value.id === VIEW_ID)
+
+/**
+ * Local search is only possible on real DAV resources within the files root
+ */
+const canSearchLocally = computed(() => {
+ if (searchStore.base) {
+ return true
+ }
+
+ const folder = filesStore.getDirectoryByPath(currentView.value.id, directory.value)
+ return folder?.isDavResource && folder?.root?.startsWith('/files/')
+})
+
+/**
+ * Different searchbox label depending if filtering or searching
+ */
+const searchLabel = computed(() => {
+ if (searchStore.scope === 'globally') {
+ return t('files', 'Search globally by filename …')
+ } else if (searchStore.scope === 'locally') {
+ return t('files', 'Search here by filename …')
+ }
+ return t('files', 'Filter file names …')
+})
+
+/**
+ * Update the search value and set the base if needed
+ * @param value - The new value
+ */
+function onUpdateSearch(value: string) {
+ if (searchStore.scope === 'locally' && currentView.value.id !== VIEW_ID) {
+ searchStore.base = filesStore.getDirectoryByPath(currentView.value.id, directory.value)
+ }
+ searchStore.query = value
+}
+</script>
+
+<template>
+ <NcAppNavigationSearch :label="searchLabel" :model-value="searchStore.query" @update:modelValue="onUpdateSearch">
+ <template #actions>
+ <NcActions :aria-label="t('files', 'Search scope options')" :disabled="isSearchView">
+ <template #icon>
+ <NcIconSvgWrapper :path="searchStore.scope === 'globally' ? mdiSearchWeb : mdiMagnify" />
+ </template>
+ <NcActionButton close-after-click @click="searchStore.scope = 'filter'">
+ <template #icon>
+ <NcIconSvgWrapper :path="mdiMagnify" />
+ </template>
+ {{ t('files', 'Filter in current view') }}
+ </NcActionButton>
+ <NcActionButton v-if="canSearchLocally" close-after-click @click="searchStore.scope = 'locally'">
+ <template #icon>
+ <NcIconSvgWrapper :path="mdiMagnify" />
+ </template>
+ {{ t('files', 'Search from this location') }}
+ </NcActionButton>
+ <NcActionButton close-after-click @click="searchStore.scope = 'globally'">
+ <template #icon>
+ <NcIconSvgWrapper :path="mdiSearchWeb" />
+ </template>
+ {{ t('files', 'Search globally') }}
+ </NcActionButton>
+ </NcActions>
+ </template>
+ </NcAppNavigationSearch>
+</template>
diff --git a/apps/files/src/composables/useBeforeNavigation.ts b/apps/files/src/composables/useBeforeNavigation.ts
new file mode 100644
index 00000000000..38b72e40fb3
--- /dev/null
+++ b/apps/files/src/composables/useBeforeNavigation.ts
@@ -0,0 +1,20 @@
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { NavigationGuard } from 'vue-router'
+
+import { onUnmounted } from 'vue'
+import { useRouter } from 'vue-router/composables'
+
+/**
+ * Helper until we use Vue-Router v4 (Vue3).
+ *
+ * @param fn - The navigation guard
+ */
+export function onBeforeNavigation(fn: NavigationGuard) {
+ const router = useRouter()
+ const remove = router.beforeResolve(fn)
+ onUnmounted(remove)
+}
diff --git a/apps/files/src/composables/useFilenameFilter.ts b/apps/files/src/composables/useFilenameFilter.ts
deleted file mode 100644
index 54c16f35384..00000000000
--- a/apps/files/src/composables/useFilenameFilter.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/*!
- * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-import { registerFileListFilter, unregisterFileListFilter } from '@nextcloud/files'
-import { watchThrottled } from '@vueuse/core'
-import { onMounted, onUnmounted, ref } from 'vue'
-import { FilenameFilter } from '../filters/FilenameFilter'
-
-/**
- * This is for the `Navigation` component to provide a filename filter
- */
-export function useFilenameFilter() {
- const searchQuery = ref('')
- const filenameFilter = new FilenameFilter()
-
- /**
- * Updating the search query ref from the filter
- * @param event The update:query event
- */
- function updateQuery(event: CustomEvent) {
- if (event.type === 'update:query') {
- searchQuery.value = event.detail
- event.stopPropagation()
- }
- }
-
- onMounted(() => {
- filenameFilter.addEventListener('update:query', updateQuery)
- registerFileListFilter(filenameFilter)
- })
- onUnmounted(() => {
- filenameFilter.removeEventListener('update:query', updateQuery)
- unregisterFileListFilter(filenameFilter.id)
- })
-
- // Update the query on the filter, but throttle to max. every 800ms
- // This will debounce the filter refresh
- watchThrottled(searchQuery, () => {
- filenameFilter.updateQuery(searchQuery.value)
- }, { throttle: 800 })
-
- return {
- searchQuery,
- }
-}
diff --git a/apps/files/src/eventbus.d.ts b/apps/files/src/eventbus.d.ts
index fb61b4a6d03..ab8dbb63dfc 100644
--- a/apps/files/src/eventbus.d.ts
+++ b/apps/files/src/eventbus.d.ts
@@ -2,7 +2,9 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import type { IFileListFilter, Node } from '@nextcloud/files'
+
+import type { IFileListFilter, Node, View } from '@nextcloud/files'
+import type { SearchScope } from './types'
declare module '@nextcloud/event-bus' {
export interface NextcloudEvents {
@@ -13,8 +15,13 @@ declare module '@nextcloud/event-bus' {
'files:favorites:removed': Node
'files:favorites:added': Node
+ 'files:filter:added': IFileListFilter
+ 'files:filter:removed': string
+ // the state of some filters has changed
'files:filters:changed': undefined
+ 'files:navigation:changed': View
+
'files:node:created': Node
'files:node:deleted': Node
'files:node:updated': Node
@@ -22,8 +29,7 @@ declare module '@nextcloud/event-bus' {
'files:node:renamed': Node
'files:node:moved': { node: Node, oldSource: string }
- 'files:filter:added': IFileListFilter
- 'files:filter:removed': string
+ 'files:search:updated': { query: string, scope: SearchScope }
}
}
diff --git a/apps/files/src/filters/FilenameFilter.ts b/apps/files/src/filters/FilenameFilter.ts
index 5019ca42d83..7914142f6ca 100644
--- a/apps/files/src/filters/FilenameFilter.ts
+++ b/apps/files/src/filters/FilenameFilter.ts
@@ -4,17 +4,31 @@
*/
import type { IFileListFilterChip, INode } from '@nextcloud/files'
-import { FileListFilter } from '@nextcloud/files'
+
+import { subscribe } from '@nextcloud/event-bus'
+import { FileListFilter, registerFileListFilter } from '@nextcloud/files'
+
+/**
+ * Register the filename filter
+ */
+export function registerFilenameFilter() {
+ registerFileListFilter(new FilenameFilter())
+}
/**
* Simple file list filter controlled by the Navigation search box
*/
-export class FilenameFilter extends FileListFilter {
+class FilenameFilter extends FileListFilter {
private searchQuery = ''
constructor() {
super('files:filename', 5)
+ subscribe('files:search:updated', ({ query, scope }) => {
+ if (scope === 'filter') {
+ this.updateQuery(query)
+ }
+ })
}
public filter(nodes: INode[]): INode[] {
diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts
index 492ffbb1915..a9aedb5fb63 100644
--- a/apps/files/src/init.ts
+++ b/apps/files/src/init.ts
@@ -26,13 +26,16 @@ import { registerTemplateEntries } from './newMenu/newFromTemplate.ts'
import { registerFavoritesView } from './views/favorites.ts'
import registerRecentView from './views/recent'
import registerPersonalFilesView from './views/personal-files'
-import registerFilesView from './views/files'
+import { registerFilesView } from './views/files'
import { registerFolderTreeView } from './views/folderTree.ts'
+import { registerSearchView } from './views/search.ts'
+
import registerPreviewServiceWorker from './services/ServiceWorker.js'
import { initLivePhotos } from './services/LivePhotos'
import { isPublicShare } from '@nextcloud/sharing/public'
import { registerConvertActions } from './actions/convertAction.ts'
+import { registerFilenameFilter } from './filters/FilenameFilter.ts'
// Register file actions
registerConvertActions()
@@ -56,8 +59,9 @@ registerTemplateEntries()
if (isPublicShare() === false) {
registerFavoritesView()
registerFilesView()
- registerRecentView()
registerPersonalFilesView()
+ registerRecentView()
+ registerSearchView()
registerFolderTreeView()
}
@@ -65,6 +69,7 @@ if (isPublicShare() === false) {
registerHiddenFilesFilter()
registerTypeFilter()
registerModifiedFilter()
+registerFilenameFilter()
// Register preview service worker
registerPreviewServiceWorker()
diff --git a/apps/files/src/router/router.ts b/apps/files/src/router/router.ts
index 00f08c38d31..20c252d6954 100644
--- a/apps/files/src/router/router.ts
+++ b/apps/files/src/router/router.ts
@@ -11,7 +11,6 @@ import Router, { isNavigationFailure, NavigationFailureType } from 'vue-router'
import Vue from 'vue'
import { useFilesStore } from '../store/files'
-import { useNavigation } from '../composables/useNavigation'
import { usePathsStore } from '../store/paths'
import logger from '../logger'
@@ -74,14 +73,27 @@ const router = new Router({
},
})
+// Handle aborted navigation (NavigationGuards) gracefully
+router.onError((error) => {
+ if (isNavigationFailure(error, NavigationFailureType.aborted)) {
+ logger.debug('Navigation was aboorted', { error })
+ } else {
+ throw error
+ }
+})
+
// If navigating back from a folder to a parent folder,
// we need to keep the current dir fileid so it's highlighted
// and scrolled into view.
-router.beforeEach((to, from, next) => {
+router.beforeResolve((to, from, next) => {
if (to.params?.parentIntercept) {
delete to.params.parentIntercept
- next()
- return
+ return next()
+ }
+
+ if (to.params.view !== from.params.view) {
+ // skip if different views
+ return next()
}
const fromDir = (from.query?.dir || '/') as string
@@ -89,17 +101,16 @@ router.beforeEach((to, from, next) => {
// We are going back to a parent directory
if (relative(fromDir, toDir) === '..') {
- const { currentView } = useNavigation()
const { getNode } = useFilesStore()
const { getPath } = usePathsStore()
- if (!currentView.value?.id) {
+ if (!from.params.view) {
logger.error('No current view id found, cannot navigate to parent directory', { fromDir, toDir })
return next()
}
// Get the previous parent's file id
- const fromSource = getPath(currentView.value?.id, fromDir)
+ const fromSource = getPath(from.params.view, fromDir)
if (!fromSource) {
logger.error('No source found for the parent directory', { fromDir, toDir })
return next()
@@ -112,7 +123,7 @@ router.beforeEach((to, from, next) => {
}
logger.debug('Navigating back to parent directory', { fromDir, toDir, fileId })
- next({
+ return next({
name: 'filelist',
query: to.query,
params: {
diff --git a/apps/files/src/services/Search.spec.ts b/apps/files/src/services/Search.spec.ts
new file mode 100644
index 00000000000..c2840521a15
--- /dev/null
+++ b/apps/files/src/services/Search.spec.ts
@@ -0,0 +1,61 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { createPinia, setActivePinia } from 'pinia'
+import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
+import { getContents } from './Search.ts'
+import { Folder, Permission } from '@nextcloud/files'
+
+const searchNodes = vi.hoisted(() => vi.fn())
+vi.mock('./WebDavSearch.ts', () => ({ searchNodes }))
+vi.mock('@nextcloud/auth')
+
+describe('Search service', () => {
+ const fakeFolder = new Folder({ owner: 'owner', source: 'https://cloud.example.com/remote.php/dav/files/owner/folder', root: '/files/owner' })
+
+ beforeAll(() => {
+ window.OCP ??= {}
+ window.OCP.Files ??= {}
+ window.OCP.Files.Router ??= { params: {}, query: {} }
+ vi.spyOn(window.OCP.Files.Router, 'params', 'get').mockReturnValue({ view: 'files' })
+ })
+
+ beforeEach(() => {
+ vi.restoreAllMocks()
+ setActivePinia(createPinia())
+ })
+
+ it('rejects on error', async () => {
+ searchNodes.mockImplementationOnce(() => { throw new Error('expected error') })
+ expect(getContents).rejects.toThrow('expected error')
+ })
+
+ it('returns the search results and a fake root', async () => {
+ searchNodes.mockImplementationOnce(() => [fakeFolder])
+ const { contents, folder } = await getContents()
+
+ expect(searchNodes).toHaveBeenCalledOnce()
+ expect(contents).toHaveLength(1)
+ expect(contents).toEqual([fakeFolder])
+ // read only root
+ expect(folder.permissions).toBe(Permission.READ)
+ })
+
+ it('can be cancelled', async () => {
+ const { promise, resolve } = Promise.withResolvers<Event>()
+ searchNodes.mockImplementationOnce(async (_, { signal }: { signal: AbortSignal}) => {
+ signal.addEventListener('abort', resolve)
+ await promise
+ return []
+ })
+
+ const content = getContents()
+ content.cancel()
+
+ // its cancelled thus the promise returns the event
+ const event = await promise
+ expect(event.type).toBe('abort')
+ })
+})
diff --git a/apps/files/src/services/Search.ts b/apps/files/src/services/Search.ts
new file mode 100644
index 00000000000..ae6f1ee50e0
--- /dev/null
+++ b/apps/files/src/services/Search.ts
@@ -0,0 +1,44 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { ContentsWithRoot } from '@nextcloud/files'
+
+import { getCurrentUser } from '@nextcloud/auth'
+import { Folder, Permission } from '@nextcloud/files'
+import { defaultRemoteURL } from '@nextcloud/files/dav'
+import { CancelablePromise } from 'cancelable-promise'
+import { searchNodes } from './WebDavSearch.ts'
+import logger from '../logger.ts'
+import { useSearchStore } from '../store/search.ts'
+import { getPinia } from '../store/index.ts'
+
+/**
+ * Get the contents for a search view
+ */
+export function getContents(): CancelablePromise<ContentsWithRoot> {
+ const controller = new AbortController()
+
+ const searchStore = useSearchStore(getPinia())
+ const dir = searchStore.base?.path
+
+ return new CancelablePromise<ContentsWithRoot>(async (resolve, reject, cancel) => {
+ cancel(() => controller.abort())
+ try {
+ const contents = await searchNodes(searchStore.query, { dir, signal: controller.signal })
+ resolve({
+ contents,
+ folder: new Folder({
+ id: 0,
+ source: `${defaultRemoteURL}#search`,
+ owner: getCurrentUser()!.uid,
+ permissions: Permission.READ,
+ }),
+ })
+ } catch (error) {
+ logger.error('Failed to fetch search results', { error })
+ reject(error)
+ }
+ })
+}
diff --git a/apps/files/src/services/WebDavSearch.ts b/apps/files/src/services/WebDavSearch.ts
new file mode 100644
index 00000000000..feb7f30b357
--- /dev/null
+++ b/apps/files/src/services/WebDavSearch.ts
@@ -0,0 +1,83 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { INode } from '@nextcloud/files'
+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 { client } from './WebdavClient.ts'
+import logger from '../logger.ts'
+
+export interface SearchNodesOptions {
+ dir?: string,
+ signal?: AbortSignal
+}
+
+/**
+ * Search for nodes matching the given query.
+ *
+ * @param query - Search query
+ * @param options - Options
+ * @param options.dir - The base directory to scope the search to
+ * @param options.signal - Abort signal for the request
+ */
+export async function searchNodes(query: string, { dir, signal }: SearchNodesOptions): Promise<INode[]> {
+ const user = getCurrentUser()
+ if (!user) {
+ // the search plugin only works for user roots
+ return []
+ }
+
+ query = query.trim()
+ if (query.length < 3) {
+ // the search plugin only works with queries of at least 3 characters
+ return []
+ }
+
+ if (dir && !dir.startsWith('/')) {
+ dir = `/${dir}`
+ }
+
+ logger.debug('Searching for nodes', { query, dir })
+ const { data } = await client.search('/', {
+ details: true,
+ signal,
+ data: `
+<d:searchrequest ${getDavNameSpaces()}>
+ <d:basicsearch>
+ <d:select>
+ <d:prop>
+ ${getDavProperties()}
+ </d:prop>
+ </d:select>
+ <d:from>
+ <d:scope>
+ <d:href>/files/${user.uid}${dir || ''}</d:href>
+ <d:depth>infinity</d:depth>
+ </d:scope>
+ </d:from>
+ <d:where>
+ <d:like>
+ <d:prop>
+ <d:displayname/>
+ </d:prop>
+ <d:literal>%${query.replace('%', '')}%</d:literal>
+ </d:like>
+ </d:where>
+ <d:orderby/>
+ </d:basicsearch>
+</d:searchrequest>`,
+ }) as ResponseDataDetailed<SearchResult>
+
+ // check if the request was aborted
+ if (signal?.aborted) {
+ return []
+ }
+
+ // otherwise return the result mapped to Nextcloud nodes
+ return data.results.map((result) => resultToNode(result, defaultRootPath, getBaseUrl()))
+}
diff --git a/apps/files/src/store/files.ts b/apps/files/src/store/files.ts
index 295704c880b..3591832d0c4 100644
--- a/apps/files/src/store/files.ts
+++ b/apps/files/src/store/files.ts
@@ -54,13 +54,13 @@ export const useFilesStore = function(...args) {
actions: {
/**
- * Get cached child nodes within a given path
+ * Get cached directory matching a given path
*
- * @param service The service (files view)
- * @param path The path relative within the service
- * @return Array of cached nodes within the path
+ * @param service - The service (files view)
+ * @param path - The path relative within the service
+ * @return The folder if found
*/
- getNodesByPath(service: string, path?: string): Node[] {
+ getDirectoryByPath(service: string, path?: string): Folder | undefined {
const pathsStore = usePathsStore()
let folder: Folder | undefined
@@ -74,6 +74,19 @@ export const useFilesStore = function(...args) {
}
}
+ return folder
+ },
+
+ /**
+ * Get cached child nodes within a given path
+ *
+ * @param service - The service (files view)
+ * @param path - The path relative within the service
+ * @return Array of cached nodes within the path
+ */
+ getNodesByPath(service: string, path?: string): Node[] {
+ const folder = this.getDirectoryByPath(service, path)
+
// If we found a cache entry and the cache entry was already loaded (has children) then use it
return (folder?._children ?? [])
.map((source: string) => this.getNode(source))
diff --git a/apps/files/src/store/search.ts b/apps/files/src/store/search.ts
new file mode 100644
index 00000000000..286cad253fc
--- /dev/null
+++ b/apps/files/src/store/search.ts
@@ -0,0 +1,170 @@
+/*!
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { INode, View } from '@nextcloud/files'
+import type RouterService from '../services/RouterService'
+import type { SearchScope } from '../types'
+
+import { emit, subscribe } from '@nextcloud/event-bus'
+import { defineStore } from 'pinia'
+import { ref, watch } from 'vue'
+import { VIEW_ID } from '../views/search'
+import logger from '../logger'
+import debounce from 'debounce'
+
+export const useSearchStore = defineStore('search', () => {
+ /**
+ * The current search query
+ */
+ const query = ref('')
+
+ /**
+ * Where to start the search
+ */
+ const base = ref<INode>()
+
+ /**
+ * Scope of the search.
+ * Scopes:
+ * - filter: only filter current file list
+ * - locally: search from current location recursivly
+ * - globally: search everywhere
+ */
+ const scope = ref<SearchScope>('filter')
+
+ // reset the base if query is cleared
+ watch(scope, () => {
+ if (scope.value !== 'locally') {
+ base.value = undefined
+ }
+
+ updateSearch()
+ })
+
+ watch(query, (old, current) => {
+ // skip if only whitespaces changed
+ if (old.trim() === current.trim()) {
+ return
+ }
+
+ updateSearch()
+ })
+
+ // initialize the search store
+ initialize()
+
+ /**
+ * Debounced update of the current route
+ * @private
+ */
+ const updateRouter = debounce((isSearch: boolean, fileid?: number) => {
+ const router = window.OCP.Files.Router as RouterService
+ router.goToRoute(
+ undefined,
+ {
+ view: VIEW_ID,
+ ...(fileid === undefined ? {} : { fileid: String(fileid) }),
+ },
+ {
+ query: query.value,
+ },
+ isSearch,
+ )
+ })
+
+ /**
+ * Handle updating the filter if needed.
+ * Also update the search view by updating the current route if needed.
+ *
+ * @private
+ */
+ function updateSearch() {
+ // emit the search event to update the filter
+ emit('files:search:updated', { query: query.value, scope: scope.value })
+
+ const router = window.OCP.Files.Router as RouterService
+
+ // if we are on the search view and the query was unset or scope was set to 'filter' we need to move back to the files view
+ if (router.params.view === VIEW_ID && (query.value === '' || scope.value === 'filter')) {
+ scope.value = 'filter'
+ return router.goToRoute(
+ undefined,
+ {
+ view: 'files',
+ },
+ {
+ ...router.query,
+ query: undefined,
+ },
+ )
+ }
+
+ // for the filter scope we do not need to adjust the current route anymore
+ // also if the query is empty we do not need to do anything
+ if (scope.value === 'filter' || !query.value) {
+ return
+ }
+
+ // we only use the directory if we search locally
+ const fileid = scope.value === 'locally' ? base.value?.fileid : undefined
+ const isSearch = router.params.view === VIEW_ID
+
+ logger.debug('Update route for updated search query', { query: query.value, fileid, isSearch })
+ updateRouter(isSearch, fileid)
+ }
+
+ /**
+ * Event handler that resets the store if the file list view was changed.
+ *
+ * @param view - The new view that is active
+ * @private
+ */
+ function onViewChanged(view: View) {
+ if (view.id !== VIEW_ID) {
+ query.value = ''
+ scope.value = 'filter'
+ }
+ }
+
+ /**
+ * Initialize the store from the router if needed
+ */
+ function initialize() {
+ subscribe('files:navigation:changed', onViewChanged)
+
+ const router = window.OCP.Files.Router as RouterService
+ // if we initially load the search view (e.g. hard page refresh)
+ // then we need to initialize the store from the router
+ if (router.params.view === VIEW_ID) {
+ query.value = [router.query.query].flat()[0] ?? ''
+
+ if (query.value) {
+ scope.value = 'globally'
+ logger.debug('Directly navigated to search view', { query: query.value })
+ } else {
+ // we do not have any query so we need to move to the files list
+ logger.info('Directly navigated to search view without any query, redirect to files view.')
+ router.goToRoute(
+ undefined,
+ {
+ ...router.params,
+ view: 'files',
+ },
+ {
+ ...router.query,
+ query: undefined,
+ },
+ true,
+ )
+ }
+ }
+ }
+
+ return {
+ base,
+ query,
+ scope,
+ }
+})
diff --git a/apps/files/src/types.ts b/apps/files/src/types.ts
index db3de13d4eb..7e9696d31d6 100644
--- a/apps/files/src/types.ts
+++ b/apps/files/src/types.ts
@@ -111,6 +111,11 @@ export interface ActiveStore {
activeAction: FileAction|null
}
+/**
+ * Search scope for the in-files-search
+ */
+export type SearchScope = 'filter'|'locally'|'globally'
+
export interface TemplateFile {
app: string
label: string
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index 60791a2b527..89d9fed6ce5 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -160,6 +160,7 @@ import { showError, showSuccess, showWarning } from '@nextcloud/dialogs'
import { ShareType } from '@nextcloud/sharing'
import { UploadPicker, UploadStatus } from '@nextcloud/upload'
import { loadState } from '@nextcloud/initial-state'
+import { useThrottleFn } from '@vueuse/core'
import { defineComponent } from 'vue'
import NcAppContent from '@nextcloud/vue/components/NcAppContent'
@@ -325,16 +326,7 @@ export default defineComponent({
return
}
- if (this.directory === '/') {
- return this.filesStore.getRoot(this.currentView.id)
- }
-
- const source = this.pathsStore.getPath(this.currentView.id, this.directory)
- if (source === undefined) {
- return
- }
-
- return this.filesStore.getNode(source) as Folder
+ return this.filesStore.getDirectoryByPath(this.currentView.id, this.directory)
},
dirContents(): Node[] {
@@ -479,6 +471,10 @@ export default defineComponent({
const hidden = this.dirContents.length - this.dirContentsFiltered.length
return getSummaryFor(this.dirContentsFiltered, hidden)
},
+
+ debouncedFetchContent() {
+ return useThrottleFn(this.fetchContent, 800, true)
+ },
},
watch: {
@@ -540,14 +536,16 @@ export default defineComponent({
// filter content if filter were changed
subscribe('files:filters:changed', this.filterDirContent)
+ subscribe('files:search:updated', this.onUpdateSearch)
+
// Finally, fetch the current directory contents
await this.fetchContent()
if (this.fileId) {
// If we have a fileId, let's check if the file exists
- const node = this.dirContents.find(node => node.fileid.toString() === this.fileId.toString())
+ const node = this.dirContents.find(node => node.fileid?.toString() === this.fileId?.toString())
// If the file isn't in the current directory nor if
// the current directory is the file, we show an error
- if (!node && this.currentFolder.fileid.toString() !== this.fileId.toString()) {
+ if (!node && this.currentFolder?.fileid?.toString() !== this.fileId.toString()) {
showError(t('files', 'The file could not be found'))
}
}
@@ -557,9 +555,17 @@ export default defineComponent({
unsubscribe('files:node:deleted', this.onNodeDeleted)
unsubscribe('files:node:updated', this.onUpdatedNode)
unsubscribe('files:config:updated', this.fetchContent)
+ unsubscribe('files:filters:changed', this.filterDirContent)
+ unsubscribe('files:search:updated', this.onUpdateSearch)
},
methods: {
+ onUpdateSearch({ query, scope }) {
+ if (query && scope !== 'filter') {
+ this.debouncedFetchContent()
+ }
+ },
+
async fetchContent() {
this.loading = true
this.error = null
diff --git a/apps/files/src/views/Navigation.cy.ts b/apps/files/src/views/Navigation.cy.ts
index a88878e2d3a..6b03efa4f5f 100644
--- a/apps/files/src/views/Navigation.cy.ts
+++ b/apps/files/src/views/Navigation.cy.ts
@@ -10,7 +10,8 @@ import NavigationView from './Navigation.vue'
import { useViewConfigStore } from '../store/viewConfig'
import { Folder, View, getNavigation } from '@nextcloud/files'
-import router from '../router/router'
+import router from '../router/router.ts'
+import RouterService from '../services/RouterService'
const resetNavigation = () => {
const nav = getNavigation()
@@ -27,9 +28,18 @@ const createView = (id: string, name: string, parent?: string) => new View({
parent,
})
+function mockWindow() {
+ window.OCP ??= {}
+ window.OCP.Files ??= {}
+ window.OCP.Files.Router = new RouterService(router)
+}
+
describe('Navigation renders', () => {
- before(() => {
+ before(async () => {
delete window._nc_navigation
+ mockWindow()
+ getNavigation().register(createView('files', 'Files'))
+ await router.replace({ name: 'filelist', params: { view: 'files' } })
cy.mockInitialState('files', 'storageStats', {
used: 1000 * 1000 * 1000,
@@ -41,6 +51,7 @@ describe('Navigation renders', () => {
it('renders', () => {
cy.mount(NavigationView, {
+ router,
global: {
plugins: [createTestingPinia({
createSpy: cy.spy,
@@ -60,6 +71,7 @@ describe('Navigation API', () => {
before(async () => {
delete window._nc_navigation
Navigation = getNavigation()
+ mockWindow()
await router.replace({ name: 'filelist', params: { view: 'files' } })
})
@@ -152,14 +164,18 @@ describe('Navigation API', () => {
})
describe('Quota rendering', () => {
- before(() => {
+ before(async () => {
delete window._nc_navigation
+ mockWindow()
+ getNavigation().register(createView('files', 'Files'))
+ await router.replace({ name: 'filelist', params: { view: 'files' } })
})
afterEach(() => cy.unmockInitialState())
it('Unknown quota', () => {
cy.mount(NavigationView, {
+ router,
global: {
plugins: [createTestingPinia({
createSpy: cy.spy,
@@ -177,6 +193,7 @@ describe('Quota rendering', () => {
})
cy.mount(NavigationView, {
+ router,
global: {
plugins: [createTestingPinia({
createSpy: cy.spy,
@@ -197,6 +214,7 @@ describe('Quota rendering', () => {
})
cy.mount(NavigationView, {
+ router,
global: {
plugins: [createTestingPinia({
createSpy: cy.spy,
@@ -219,6 +237,7 @@ describe('Quota rendering', () => {
})
cy.mount(NavigationView, {
+ router,
global: {
plugins: [createTestingPinia({
createSpy: cy.spy,
diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue
index 3147268f34d..c424a0d74b8 100644
--- a/apps/files/src/views/Navigation.vue
+++ b/apps/files/src/views/Navigation.vue
@@ -7,7 +7,7 @@
class="files-navigation"
:aria-label="t('files', 'Files')">
<template #search>
- <NcAppNavigationSearch v-model="searchQuery" :label="t('files', 'Filter file names …')" />
+ <FilesNavigationSearch />
</template>
<template #default>
<NcAppNavigationList class="files-navigation__list"
@@ -39,24 +39,24 @@
</template>
<script lang="ts">
-import { getNavigation, type View } from '@nextcloud/files'
+import type { View } from '@nextcloud/files'
import type { ViewConfig } from '../types.ts'
-import { defineComponent } from 'vue'
import { emit, subscribe } from '@nextcloud/event-bus'
-import { translate as t, getCanonicalLocale, getLanguage } from '@nextcloud/l10n'
+import { getNavigation } from '@nextcloud/files'
+import { t, getCanonicalLocale, getLanguage } from '@nextcloud/l10n'
+import { defineComponent } from 'vue'
import IconCog from 'vue-material-design-icons/Cog.vue'
import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
import NcAppNavigationList from '@nextcloud/vue/components/NcAppNavigationList'
-import NcAppNavigationSearch from '@nextcloud/vue/components/NcAppNavigationSearch'
import NavigationQuota from '../components/NavigationQuota.vue'
import SettingsModal from './Settings.vue'
import FilesNavigationItem from '../components/FilesNavigationItem.vue'
+import FilesNavigationSearch from '../components/FilesNavigationSearch.vue'
import { useNavigation } from '../composables/useNavigation'
-import { useFilenameFilter } from '../composables/useFilenameFilter'
import { useFiltersStore } from '../store/filters.ts'
import { useViewConfigStore } from '../store/viewConfig.ts'
import logger from '../logger.ts'
@@ -75,12 +75,12 @@ export default defineComponent({
components: {
IconCog,
FilesNavigationItem,
+ FilesNavigationSearch,
NavigationQuota,
NcAppNavigation,
NcAppNavigationItem,
NcAppNavigationList,
- NcAppNavigationSearch,
SettingsModal,
},
@@ -88,11 +88,9 @@ export default defineComponent({
const filtersStore = useFiltersStore()
const viewConfigStore = useViewConfigStore()
const { currentView, views } = useNavigation()
- const { searchQuery } = useFilenameFilter()
return {
currentView,
- searchQuery,
t,
views,
diff --git a/apps/files/src/views/SearchEmptyView.vue b/apps/files/src/views/SearchEmptyView.vue
new file mode 100644
index 00000000000..0553e416caf
--- /dev/null
+++ b/apps/files/src/views/SearchEmptyView.vue
@@ -0,0 +1,57 @@
+<!--
+ - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<script setup lang="ts">
+import { mdiMagnifyClose } from '@mdi/js'
+import { t } from '@nextcloud/l10n'
+import debounce from 'debounce'
+import NcButton from '@nextcloud/vue/components/NcButton'
+import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
+import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
+import NcInputField from '@nextcloud/vue/components/NcInputField'
+import { getPinia } from '../store/index.ts'
+import { useSearchStore } from '../store/search.ts'
+
+const searchStore = useSearchStore(getPinia())
+const debouncedUpdate = debounce((value: string) => {
+ searchStore.query = value
+}, 500)
+</script>
+
+<template>
+ <NcEmptyContent :name="t('files', 'No search results for “{query}”', { query: searchStore.query })">
+ <template #icon>
+ <NcIconSvgWrapper :path="mdiMagnifyClose" />
+ </template>
+ <template #action>
+ <div class="search-empty-view__wrapper">
+ <NcInputField class="search-empty-view__input"
+ :label="t('files', 'Search for files')"
+ :model-value="searchStore.query"
+ type="search"
+ @update:model-value="debouncedUpdate" />
+ <NcButton v-if="searchStore.scope === 'locally'" @click="searchStore.scope = 'globally'">
+ {{ t('files', 'Search globally') }}
+ </NcButton>
+ </div>
+ </template>
+ </NcEmptyContent>
+</template>
+
+<style scoped lang="scss">
+.search-empty-view {
+ &__input {
+ flex: 0 1;
+ min-width: min(400px, 50vw);
+ }
+
+ &__wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ align-items: baseline;
+ }
+}
+</style>
diff --git a/apps/files/src/views/files.ts b/apps/files/src/views/files.ts
index a49a13f91e1..699e173de63 100644
--- a/apps/files/src/views/files.ts
+++ b/apps/files/src/views/files.ts
@@ -8,10 +8,15 @@ import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
import { getContents } from '../services/Files'
import { View, getNavigation } from '@nextcloud/files'
-export default () => {
+export const VIEW_ID = 'files'
+
+/**
+ * Register the files view to the navigation
+ */
+export function registerFilesView() {
const Navigation = getNavigation()
Navigation.register(new View({
- id: 'files',
+ id: VIEW_ID,
name: t('files', 'All files'),
caption: t('files', 'List of your files and folders.'),
diff --git a/apps/files/src/views/search.ts b/apps/files/src/views/search.ts
new file mode 100644
index 00000000000..a30f732163c
--- /dev/null
+++ b/apps/files/src/views/search.ts
@@ -0,0 +1,51 @@
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { ComponentPublicInstanceConstructor } from 'vue/types/v3-component-public-instance'
+
+import { View, getNavigation } from '@nextcloud/files'
+import { t } from '@nextcloud/l10n'
+import { getContents } from '../services/Search.ts'
+import { VIEW_ID as FILES_VIEW_ID } from './files.ts'
+import MagnifySvg from '@mdi/svg/svg/magnify.svg?raw'
+import Vue from 'vue'
+
+export const VIEW_ID = 'search'
+
+/**
+ * Register the search-in-files view
+ */
+export function registerSearchView() {
+ let instance: Vue
+ let view: ComponentPublicInstanceConstructor
+
+ const Navigation = getNavigation()
+ Navigation.register(new View({
+ id: VIEW_ID,
+ name: t('files', 'Search'),
+ caption: t('files', 'Search results within your files.'),
+
+ async emptyView(el) {
+ if (!view) {
+ view = (await import('./SearchEmptyView.vue')).default
+ } else {
+ instance.$destroy()
+ }
+ instance = new Vue(view)
+ instance.$mount(el)
+ },
+
+ icon: MagnifySvg,
+ order: 10,
+
+ parent: FILES_VIEW_ID,
+ // it should be shown expanded
+ expanded: true,
+ // this view is hidden by default and only shown when active
+ hidden: true,
+
+ getContents,
+ }))
+}
diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php
index 9ba105e240d..5281220282a 100644
--- a/apps/files/templates/index.php
+++ b/apps/files/templates/index.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/apps/files/tests/Controller/ApiControllerTest.php b/apps/files/tests/Controller/ApiControllerTest.php
index 0c9d7a4fa6e..d6d86e293fd 100644
--- a/apps/files/tests/Controller/ApiControllerTest.php
+++ b/apps/files/tests/Controller/ApiControllerTest.php
@@ -29,7 +29,6 @@ use OCP\IPreview;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
-use OCP\Share\IAttributes;
use OCP\Share\IManager;
use OCP\Share\IShare;
use PHPUnit\Framework\MockObject\MockObject;
@@ -183,16 +182,10 @@ class ApiControllerTest extends TestCase {
}
public function testGetThumbnailSharedNoDownload(): void {
- $attributes = $this->createMock(IAttributes::class);
- $attributes->expects(self::once())
- ->method('getAttribute')
- ->with('permissions', 'download')
- ->willReturn(false);
-
$share = $this->createMock(IShare::class);
$share->expects(self::once())
- ->method('getAttributes')
- ->willReturn($attributes);
+ ->method('canSeeContent')
+ ->willReturn(false);
$storage = $this->createMock(ISharedStorage::class);
$storage->expects(self::once())
@@ -221,8 +214,8 @@ class ApiControllerTest extends TestCase {
public function testGetThumbnailShared(): void {
$share = $this->createMock(IShare::class);
$share->expects(self::once())
- ->method('getAttributes')
- ->willReturn(null);
+ ->method('canSeeContent')
+ ->willReturn(true);
$storage = $this->createMock(ISharedStorage::class);
$storage->expects(self::once())