Merge branch 'master' into auth-token-commands

pull/40026/head
Lucas Azevedo 9 months ago
commit 79bc6ba06c

@ -18,6 +18,7 @@
/apps/oauth2/appinfo/info.xml @julien-nc @ChristophWurst
/apps/provisioning_api/appinfo/info.xml @provokateurin @nickvergessen
/apps/settings/appinfo/info.xml @Pytal @JuliaKirschenheuter
/apps/sharebymail/appinfo/info.xml @Altahrim
/apps/systemtags/appinfo/info.xml @Antreesy @marcelklehr
/apps/theming/appinfo/info.xml @skjnldsv @juliushaertl
/apps/twofactor_backupcodes/appinfo/info.xml @st3iny @miaulalala @ChristophWurst
@ -36,6 +37,10 @@
/apps/twofactor_backupcodes @ChristophWurst @miaulalala @nickvergessen @st3iny
/core/templates/twofactor* @ChristophWurst @miaulalala @nickvergessen @st3iny
# Limit login to IP
# Watch login routes for https://github.com/nextcloud/limit_login_to_ip
/core/routes.php @Altahrim
# OpenAPI
openapi.json @provokateurin
ResponseDefinitions.php @provokateurin

@ -43,8 +43,8 @@ OC.L10N.register(
"You deleted to-do {todo} from list {calendar}" : "Ви вилучили завдання {todo} зі списку {calendar}",
"{actor} updated to-do {todo} in list {calendar}" : "{actor} оновив(-ла) завдання {todo} у списку {calendar}",
"You updated to-do {todo} in list {calendar}" : "Ви оновили завдання {todo} у списку {calendar}",
"{actor} solved to-do {todo} in list {calendar}" : "{actor} вирішив(-ла) завдання {todo} у списку {calendar}",
"You solved to-do {todo} in list {calendar}" : "Ви вирішили завдання {todo} у списку {calendar}",
"{actor} solved to-do {todo} in list {calendar}" : "{actor} виконав(-ла) завдання {todo} зі списку {calendar}",
"You solved to-do {todo} in list {calendar}" : "Ви виконали завдання {todo} зі списку {calendar}",
"{actor} reopened to-do {todo} in list {calendar}" : "{actor} знову відкрив(-ла) завдання у списку {calendar}",
"You reopened to-do {todo} in list {calendar}" : "Ви знову відкрили завдання {todo} зі списку {calendar}",
"{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "{actor} перемістив(-ла) завдання {todo} зі списку {sourceCalendar} до списку {targetCalendar}",
@ -154,7 +154,7 @@ OC.L10N.register(
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "Точка доступу WebDAV",
"Availability" : "Доступність",
"If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Якщо ви налаштуєте свій робочий час, інші користувачі побачать, коли вас немає на місці, коли вони бронюють зустріч.",
"If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Будь ласка, налаштуйте ваш робочий час, щоби інші користувачі могли бачити, коли ви відсутні під час бронювання зустрічей.",
"Time zone:" : "Часовий пояс:",
"to" : "до",
"Delete slot" : "Вилучити діапазон",
@ -167,17 +167,17 @@ OC.L10N.register(
"Friday" : "П'ятниця",
"Saturday" : "Субота",
"Sunday" : "Неділя",
"Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлюйте статус користувача на \"Не турбувати\", коли ви не доступні, щоб вимкнути усі сповіщення.",
"Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлювати статус користувача у \"Не турбувати\", коли ви не доступні. В цей проміжок часу ви не отримуватимете сповіщення.",
"Save" : "Зберегти",
"Failed to load availability" : "Не вдалося завантажити доступність",
"Saved availability" : "Збережена наявність",
"Failed to save availability" : "Не вдалося зберегти наявність",
"Calendar server" : "Календар",
"Send invitations to attendees" : "Надіслати запрошення учасникам",
"Automatically generate a birthday calendar" : "Автоматично згенерувати календар днів народження",
"Send invitations to attendees" : "Надсилати запрошення учасникам",
"Automatically generate a birthday calendar" : "Автоматично створити календар днів народження",
"Birthday calendars will be generated by a background job." : "Календар днів народження буде згенеровано у фоновому завданні.",
"Hence they will not be available immediately after enabling but will show up after some time." : "Отже вони не будуть доступні одразу після увімкнення, але з'являться згодом.",
"Send notifications for events" : "Відправити сповіщення для подій",
"Send notifications for events" : "Надсилати сповіщення про події",
"Notifications are sent via background jobs, so these must occur often enough." : "Сповіщення надсилаються у фонових завданнях, тож вони мають запускатися достатньо часто.",
"Send reminder notifications to calendar sharees as well" : "Також надсилайте нагадування користувачам вашого спільного календаря",
"Reminders are always sent to organizers and attendees." : "Нагадування завжди надсилаються організаторам і учасникам.",

@ -41,8 +41,8 @@
"You deleted to-do {todo} from list {calendar}" : "Ви вилучили завдання {todo} зі списку {calendar}",
"{actor} updated to-do {todo} in list {calendar}" : "{actor} оновив(-ла) завдання {todo} у списку {calendar}",
"You updated to-do {todo} in list {calendar}" : "Ви оновили завдання {todo} у списку {calendar}",
"{actor} solved to-do {todo} in list {calendar}" : "{actor} вирішив(-ла) завдання {todo} у списку {calendar}",
"You solved to-do {todo} in list {calendar}" : "Ви вирішили завдання {todo} у списку {calendar}",
"{actor} solved to-do {todo} in list {calendar}" : "{actor} виконав(-ла) завдання {todo} зі списку {calendar}",
"You solved to-do {todo} in list {calendar}" : "Ви виконали завдання {todo} зі списку {calendar}",
"{actor} reopened to-do {todo} in list {calendar}" : "{actor} знову відкрив(-ла) завдання у списку {calendar}",
"You reopened to-do {todo} in list {calendar}" : "Ви знову відкрили завдання {todo} зі списку {calendar}",
"{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}" : "{actor} перемістив(-ла) завдання {todo} зі списку {sourceCalendar} до списку {targetCalendar}",
@ -152,7 +152,7 @@
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "Точка доступу WebDAV",
"Availability" : "Доступність",
"If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Якщо ви налаштуєте свій робочий час, інші користувачі побачать, коли вас немає на місці, коли вони бронюють зустріч.",
"If you configure your working hours, other users will see when you are out of office when they book a meeting." : "Будь ласка, налаштуйте ваш робочий час, щоби інші користувачі могли бачити, коли ви відсутні під час бронювання зустрічей.",
"Time zone:" : "Часовий пояс:",
"to" : "до",
"Delete slot" : "Вилучити діапазон",
@ -165,17 +165,17 @@
"Friday" : "П'ятниця",
"Saturday" : "Субота",
"Sunday" : "Неділя",
"Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлюйте статус користувача на \"Не турбувати\", коли ви не доступні, щоб вимкнути усі сповіщення.",
"Automatically set user status to \"Do not disturb\" outside of availability to mute all notifications." : "Автоматично встановлювати статус користувача у \"Не турбувати\", коли ви не доступні. В цей проміжок часу ви не отримуватимете сповіщення.",
"Save" : "Зберегти",
"Failed to load availability" : "Не вдалося завантажити доступність",
"Saved availability" : "Збережена наявність",
"Failed to save availability" : "Не вдалося зберегти наявність",
"Calendar server" : "Календар",
"Send invitations to attendees" : "Надіслати запрошення учасникам",
"Automatically generate a birthday calendar" : "Автоматично згенерувати календар днів народження",
"Send invitations to attendees" : "Надсилати запрошення учасникам",
"Automatically generate a birthday calendar" : "Автоматично створити календар днів народження",
"Birthday calendars will be generated by a background job." : "Календар днів народження буде згенеровано у фоновому завданні.",
"Hence they will not be available immediately after enabling but will show up after some time." : "Отже вони не будуть доступні одразу після увімкнення, але з'являться згодом.",
"Send notifications for events" : "Відправити сповіщення для подій",
"Send notifications for events" : "Надсилати сповіщення про події",
"Notifications are sent via background jobs, so these must occur often enough." : "Сповіщення надсилаються у фонових завданнях, тож вони мають запускатися достатньо часто.",
"Send reminder notifications to calendar sharees as well" : "Також надсилайте нагадування користувачам вашого спільного календаря",
"Reminders are always sent to organizers and attendees." : "Нагадування завжди надсилаються організаторам і учасникам.",

@ -24,22 +24,18 @@
namespace OCA\DAV\Command;
use OCA\DAV\CardDAV\SyncService;
use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class SyncSystemAddressBook extends Command {
/** @var SyncService */
private $syncService;
/**
* @param SyncService $syncService
*/
public function __construct(SyncService $syncService) {
public function __construct(private SyncService $syncService, private IConfig $config) {
parent::__construct();
$this->syncService = $syncService;
}
protected function configure() {
@ -62,6 +58,7 @@ class SyncSystemAddressBook extends Command {
$progress->finish();
$output->writeln('');
$this->config->setAppValue('dav', 'needs_system_address_book_sync', 'no');
return 0;
}
}

@ -29,6 +29,8 @@ namespace OCA\DAV\Migration;
use Closure;
use OCA\DAV\CardDAV\SyncService;
use OCP\DB\ISchemaWrapper;
use OCP\IConfig;
use OCP\IUserManager;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Psr\Container\ContainerExceptionInterface;
@ -37,22 +39,27 @@ use Psr\Log\LoggerInterface;
use Throwable;
class Version1027Date20230504122946 extends SimpleMigrationStep {
private SyncService $syncService;
private LoggerInterface $logger;
public function __construct(SyncService $syncService, LoggerInterface $logger) {
$this->syncService = $syncService;
$this->logger = $logger;
}
public function __construct(private SyncService $syncService,
private LoggerInterface $logger,
private IUserManager $userManager,
private IConfig $config) {}
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
if($this->userManager->countUsers() > 1000) {
$this->config->setAppValue('dav', 'needs_system_address_book_sync', 'yes');
$output->info('Could not sync system address books during update - too many user records have been found. Please call occ dav:sync-system-addressbook manually.');
return;
}
try {
$this->syncService->syncInstance();
$this->config->setAppValue('dav', 'needs_system_address_book_sync', 'no');
} catch (Throwable $e) {
$this->config->setAppValue('dav', 'needs_system_address_book_sync', 'yes');
$this->logger->error('Could not sync system address books during update', [
'exception' => $e,
]);

@ -7,7 +7,7 @@ OC.L10N.register(
"Cancel" : "Скасувати",
"Add remote share" : "Додати віддалений каталог",
"Invalid Federated Cloud ID" : "Неправильний Об'єднаний Хмарний Ідентіфікатор ",
"Server to server sharing is not enabled on this server" : "На даному сервері вимкнута можливість передачі даних між серверами",
"Server to server sharing is not enabled on this server" : "На цьому сервері вимкнута можливість передавання даних між серверами",
"Couldn't establish a federated share." : "Не вдалося встановити об’єднаний спільний доступ.",
"Couldn't establish a federated share, maybe the password was wrong." : "Не вдалося встановити спільний доступ до об'єднаної хмари, можливо, пароль неправильний.",
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Запит на Federated Share надіслано, ви отримаєте запрошення. Перевірте сповіщення.",

@ -5,7 +5,7 @@
"Cancel" : "Скасувати",
"Add remote share" : "Додати віддалений каталог",
"Invalid Federated Cloud ID" : "Неправильний Об'єднаний Хмарний Ідентіфікатор ",
"Server to server sharing is not enabled on this server" : "На даному сервері вимкнута можливість передачі даних між серверами",
"Server to server sharing is not enabled on this server" : "На цьому сервері вимкнута можливість передавання даних між серверами",
"Couldn't establish a federated share." : "Не вдалося встановити об’єднаний спільний доступ.",
"Couldn't establish a federated share, maybe the password was wrong." : "Не вдалося встановити спільний доступ до об'єднаної хмари, можливо, пароль неправильний.",
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Запит на Federated Share надіслано, ви отримаєте запрошення. Перевірте сповіщення.",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "حدث خطأ غير معروف",
"File could not be uploaded" : "لا يمكن تحميل الملف ",
"Uploading …" : "جاري الرفع...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "التحديث جارٍ … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} من {totalSize} ({bitrate})",
"Uploading that item is not supported" : "رفع هذا النوع الملفات غير مدعوم",
"Target folder does not exist any more" : "المجلد المراد غير موجود بعد الان",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "حدث خطأ غير معروف",
"File could not be uploaded" : "لا يمكن تحميل الملف ",
"Uploading …" : "جاري الرفع...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "التحديث جارٍ … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} من {totalSize} ({bitrate})",
"Uploading that item is not supported" : "رفع هذا النوع الملفات غير مدعوم",
"Target folder does not exist any more" : "المجلد المراد غير موجود بعد الان",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "S'ha produït un error desconegut",
"File could not be uploaded" : "No s'ha pogut pujar el fitxer",
"Uploading …" : "S'està pujant…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "S'està pujant… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "No s'admet la pujada aquest element",
"Target folder does not exist any more" : "La carpeta de destinació ja no existeix",
@ -175,6 +177,7 @@ OC.L10N.register(
"Rename file" : "Canvia el nom del fitxer",
"File name" : "Nom del fitxer",
"A long time ago" : "Fa molt de temps",
"This node is unavailable" : "Aquest node no està disponible",
"Download file {name}" : "Baixa el fitxer {name}",
"\"{displayName}\" action executed successfully" : "L'acció «{displayName}» s'ha executat correctament",
"\"{displayName}\" action failed" : "S'ha produït un error en l'acció «{displayName}»",
@ -252,6 +255,7 @@ OC.L10N.register(
"No favorites yet" : "Encara no teniu preferits",
"Files and folders you mark as favorite will show up here" : "Els fitxers i les carpetes que marqueu com a preferits es mostraran aquí",
"All files" : "Tots els fitxers",
"List of your files and folders." : "Llista dels vostres fitxers i carpetes.",
"List of recently modified files and folders." : "Llista de fitxers i carpetes modificats recentment.",
"No recently modified files" : "No hi ha cap fitxer modificat recentment",
"Files and folders you recently modified will show up here." : "Els fitxers i les carpetes que heu modificat recentment es mostraran aquí",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "S'ha produït un error desconegut",
"File could not be uploaded" : "No s'ha pogut pujar el fitxer",
"Uploading …" : "S'està pujant…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "S'està pujant… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "No s'admet la pujada aquest element",
"Target folder does not exist any more" : "La carpeta de destinació ja no existeix",
@ -173,6 +175,7 @@
"Rename file" : "Canvia el nom del fitxer",
"File name" : "Nom del fitxer",
"A long time ago" : "Fa molt de temps",
"This node is unavailable" : "Aquest node no està disponible",
"Download file {name}" : "Baixa el fitxer {name}",
"\"{displayName}\" action executed successfully" : "L'acció «{displayName}» s'ha executat correctament",
"\"{displayName}\" action failed" : "S'ha produït un error en l'acció «{displayName}»",
@ -250,6 +253,7 @@
"No favorites yet" : "Encara no teniu preferits",
"Files and folders you mark as favorite will show up here" : "Els fitxers i les carpetes que marqueu com a preferits es mostraran aquí",
"All files" : "Tots els fitxers",
"List of your files and folders." : "Llista dels vostres fitxers i carpetes.",
"List of recently modified files and folders." : "Llista de fitxers i carpetes modificats recentment.",
"No recently modified files" : "No hi ha cap fitxer modificat recentment",
"Files and folders you recently modified will show up here." : "Els fitxers i les carpetes que heu modificat recentment es mostraran aquí",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Vyskytla se neznámá chyba",
"File could not be uploaded" : "Soubor se nepodařilo nahrát",
"Uploading …" : "Nahrávání…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Nahrávání… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} z {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Nahrání této položky není podporováno",
"Target folder does not exist any more" : "Cílová složka už neexistuje",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Vyskytla se neznámá chyba",
"File could not be uploaded" : "Soubor se nepodařilo nahrát",
"Uploading …" : "Nahrávání…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Nahrávání… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} z {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Nahrání této položky není podporováno",
"Target folder does not exist any more" : "Cílová složka už neexistuje",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Es ist ein unbekannter Fehler aufgetreten",
"File could not be uploaded" : "Datei konnte nicht hochgeladen werden.",
"Uploading …" : "Lade hoch …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Lade hoch … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} von {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Hochladen von Daten dieser Art wird nicht unterstützt.",
"Target folder does not exist any more" : "Zielordner existiert nicht mehr",
@ -32,6 +34,7 @@ OC.L10N.register(
"Move" : "Verschieben",
"Copy" : "Kopieren",
"Choose target folder" : "Zielordner wählen",
"Set reminder" : "Erinnerung erstellen",
"Edit locally" : "Lokal bearbeiten",
"Open" : "Öffnen",
"Delete file" : "Datei löschen",
@ -108,6 +111,8 @@ OC.L10N.register(
"Create new folder" : "Neuen Ordner erstellen",
"Upload file" : "Datei hochladen",
"Recent" : "Neueste",
"This file has the tag {tag}" : "Diese Datei trägt das Schlagwort {tag}.",
"This file has the tags {firstTags} and {lastTag}" : "Diese Datei trägt die Schlagworte {firstTags} und {lastTag}.",
"Not favorited" : "Nicht favorisiert",
"Remove from favorites" : "Von Favoriten entfernen",
"Add to favorites" : "Zu den Favoriten hinzufügen",
@ -169,11 +174,21 @@ OC.L10N.register(
"Reload current directory" : "Aktuelles Verzeichnis neu laden",
"Go to the \"{dir}\" directory" : "In das Verzeichnis \"{dir}“ wechseln",
"Select the row for {displayName}" : "Zeile für {displayName} auswählen",
"Rename file" : "Datei umbenennen",
"File name" : "Dateiname",
"A long time ago" : "Vor langer Zeit",
"This node is unavailable" : "Dieser Knoten ist nicht verfügbar.",
"Download file {name}" : "Datei {name} herunterladen",
"\"{displayName}\" action executed successfully" : "Aktion \"{displayName}\" erfolgreich ausgeführt",
"\"{displayName}\" action failed" : "Aktion \"{displayName}\" fehlgeschlagen",
"\"{name}\" is not an allowed filetype." : "\"{name}\" ist kein zulässiger Dateityp.",
"{newName} already exists." : "{newName} existiert bereits.",
"Name cannot be empty" : "Der Name darf nicht leer sein",
"Another entry with the same name already exists" : "Ein anderer Eintrag mit diesem Namen existiert bereits.",
"Renamed \"{oldName}\" to \"{newName}\"" : "\"{oldName}\" in \"{newName}\" umbenannt",
"Could not rename \"{oldName}\", it does not exist any more" : "\"{oldName}\" konnte nicht umbenannt werden, da es nicht mehr existiert.",
"The name \"{newName}\"\" is already used in the folder \"{dir}\". Please choose a different name." : "Der Name \"{targetName}\" wird bereits im Ordner \"{dir}\" benutzt. Bitte wähle einen anderen Namen.",
"Could not rename \"{oldName}\"" : "\"{oldName}\" konnte nicht umbenannt werden.",
"Total rows summary" : "Zusammenfassung aller Zeilen",
"\"{displayName}\" failed on some elements " : "\"{displayName}\" ist bei einigen Elementen fehlgeschlagen",
"\"{displayName}\" batch action executed successfully" : "Stapelaktion \"{displayName}\" erfolgreich ausgeführt",
@ -182,6 +197,8 @@ OC.L10N.register(
"Sort list by {column} ({direction})" : "Liste sortieren nach {column} ({direction})",
"Select all" : "Alle auswählen",
"Unselect all" : "Auswahl aufheben",
"List of files and folders." : "Liste der Dateien und Ordner",
"This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Diese Liste wird aus Performance-Gründen nicht vollständig angezeigt. Die Dateien werden angezeigt, wenn du durch die Liste navigierst.",
"File not found" : "Datei nicht gefunden",
"Storage informations" : "Speicherinformationen",
"{usedQuotaByte} used" : "{usedQuotaByte} verwendet",
@ -208,6 +225,7 @@ OC.L10N.register(
"Files settings" : "Dateien-Einstellungen",
"File cannot be accessed" : "Auf die Datei kann nicht zugegriffen werden",
"You might not have have permissions to view it, ask the sender to share it" : "Möglicherweise hast du nicht die Berechtigung zur Anzeige. Bitte den Absender, die Datei freizugeben.",
"Sort favorites first" : "Favoriten zuerst sortieren",
"Show hidden files" : "Versteckte Dateien anzeigen",
"Crop image previews" : "Bildvorschauen zuschneiden",
"Additional settings" : "Zusätzliche Einstellungen",
@ -226,13 +244,21 @@ OC.L10N.register(
"Blank" : "Leer",
"Unable to create new file from template" : "Neue Datei konnte nicht aus Vorlage erstellt werden",
"Delete permanently" : "Endgültig löschen",
"Open folder {displayName}" : "Ordner {displayName} öffnen",
"Open in Files" : "In App \"Dateien\" öffnen",
"Open details" : "Details öffnen",
"Set up templates folder" : "Vorlagenordner einrichten",
"Templates" : "Vorlagen",
"Create new templates folder" : "Neuen Vorlagenordner erstellen",
"Unable to initialize the templates directory" : "Der Vorlagenordner konnte nicht initialisiert werden",
"List of favorites files and folders." : "Liste der favorisierten Ordner und Dateien",
"No favorites yet" : "Noch keine Favoriten vorhanden",
"Files and folders you mark as favorite will show up here" : "Dateien und Ordner, die als Favoriten markiert werden, erscheinen hier",
"All files" : "Alle Dateien",
"List of your files and folders." : "Liste deiner Dateien und Ordner",
"List of recently modified files and folders." : "Liste der zuletzt geänderten Dateien und Ordner",
"No recently modified files" : "Keine kürzlich geänderten Dateien",
"Files and folders you recently modified will show up here." : "Die von dir kürzlich geänderten Dateien und Ordner werden hier angezeigt.",
"Toggle %1$s sublist" : "Unterliste %1$s umschalten",
"No entries found in this folder" : "Keine Einträge in diesem Ordner gefunden",
"Upload too large" : "Der Upload ist zu groß",
@ -256,7 +282,7 @@ OC.L10N.register(
"Deleted shares" : "Gelöschte Freigaben",
"Pending shares" : "Ausstehende Freigaben",
"Open folder {name}" : "Ordner {name} öffnen",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Diese Liste ist aus Performance-Gründen nicht vollständig gerendert. Die Dateien werden gerendert, wenn du durch die Liste navigierst.",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Diese Liste wird aus Performance-Gründen nicht vollständig angezeigt. Die Dateien werden gerendert, wenn du durch die Liste navigierst.",
"Search for an account" : "Nach einem Konto suchen",
"Choose" : "Auswählen",
"No files or folders have been deleted yet" : "Es wurden noch keine Dateien oder Ordner gelöscht"

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Es ist ein unbekannter Fehler aufgetreten",
"File could not be uploaded" : "Datei konnte nicht hochgeladen werden.",
"Uploading …" : "Lade hoch …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Lade hoch … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} von {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Hochladen von Daten dieser Art wird nicht unterstützt.",
"Target folder does not exist any more" : "Zielordner existiert nicht mehr",
@ -30,6 +32,7 @@
"Move" : "Verschieben",
"Copy" : "Kopieren",
"Choose target folder" : "Zielordner wählen",
"Set reminder" : "Erinnerung erstellen",
"Edit locally" : "Lokal bearbeiten",
"Open" : "Öffnen",
"Delete file" : "Datei löschen",
@ -106,6 +109,8 @@
"Create new folder" : "Neuen Ordner erstellen",
"Upload file" : "Datei hochladen",
"Recent" : "Neueste",
"This file has the tag {tag}" : "Diese Datei trägt das Schlagwort {tag}.",
"This file has the tags {firstTags} and {lastTag}" : "Diese Datei trägt die Schlagworte {firstTags} und {lastTag}.",
"Not favorited" : "Nicht favorisiert",
"Remove from favorites" : "Von Favoriten entfernen",
"Add to favorites" : "Zu den Favoriten hinzufügen",
@ -167,11 +172,21 @@
"Reload current directory" : "Aktuelles Verzeichnis neu laden",
"Go to the \"{dir}\" directory" : "In das Verzeichnis \"{dir}“ wechseln",
"Select the row for {displayName}" : "Zeile für {displayName} auswählen",
"Rename file" : "Datei umbenennen",
"File name" : "Dateiname",
"A long time ago" : "Vor langer Zeit",
"This node is unavailable" : "Dieser Knoten ist nicht verfügbar.",
"Download file {name}" : "Datei {name} herunterladen",
"\"{displayName}\" action executed successfully" : "Aktion \"{displayName}\" erfolgreich ausgeführt",
"\"{displayName}\" action failed" : "Aktion \"{displayName}\" fehlgeschlagen",
"\"{name}\" is not an allowed filetype." : "\"{name}\" ist kein zulässiger Dateityp.",
"{newName} already exists." : "{newName} existiert bereits.",
"Name cannot be empty" : "Der Name darf nicht leer sein",
"Another entry with the same name already exists" : "Ein anderer Eintrag mit diesem Namen existiert bereits.",
"Renamed \"{oldName}\" to \"{newName}\"" : "\"{oldName}\" in \"{newName}\" umbenannt",
"Could not rename \"{oldName}\", it does not exist any more" : "\"{oldName}\" konnte nicht umbenannt werden, da es nicht mehr existiert.",
"The name \"{newName}\"\" is already used in the folder \"{dir}\". Please choose a different name." : "Der Name \"{targetName}\" wird bereits im Ordner \"{dir}\" benutzt. Bitte wähle einen anderen Namen.",
"Could not rename \"{oldName}\"" : "\"{oldName}\" konnte nicht umbenannt werden.",
"Total rows summary" : "Zusammenfassung aller Zeilen",
"\"{displayName}\" failed on some elements " : "\"{displayName}\" ist bei einigen Elementen fehlgeschlagen",
"\"{displayName}\" batch action executed successfully" : "Stapelaktion \"{displayName}\" erfolgreich ausgeführt",
@ -180,6 +195,8 @@
"Sort list by {column} ({direction})" : "Liste sortieren nach {column} ({direction})",
"Select all" : "Alle auswählen",
"Unselect all" : "Auswahl aufheben",
"List of files and folders." : "Liste der Dateien und Ordner",
"This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Diese Liste wird aus Performance-Gründen nicht vollständig angezeigt. Die Dateien werden angezeigt, wenn du durch die Liste navigierst.",
"File not found" : "Datei nicht gefunden",
"Storage informations" : "Speicherinformationen",
"{usedQuotaByte} used" : "{usedQuotaByte} verwendet",
@ -206,6 +223,7 @@
"Files settings" : "Dateien-Einstellungen",
"File cannot be accessed" : "Auf die Datei kann nicht zugegriffen werden",
"You might not have have permissions to view it, ask the sender to share it" : "Möglicherweise hast du nicht die Berechtigung zur Anzeige. Bitte den Absender, die Datei freizugeben.",
"Sort favorites first" : "Favoriten zuerst sortieren",
"Show hidden files" : "Versteckte Dateien anzeigen",
"Crop image previews" : "Bildvorschauen zuschneiden",
"Additional settings" : "Zusätzliche Einstellungen",
@ -224,13 +242,21 @@
"Blank" : "Leer",
"Unable to create new file from template" : "Neue Datei konnte nicht aus Vorlage erstellt werden",
"Delete permanently" : "Endgültig löschen",
"Open folder {displayName}" : "Ordner {displayName} öffnen",
"Open in Files" : "In App \"Dateien\" öffnen",
"Open details" : "Details öffnen",
"Set up templates folder" : "Vorlagenordner einrichten",
"Templates" : "Vorlagen",
"Create new templates folder" : "Neuen Vorlagenordner erstellen",
"Unable to initialize the templates directory" : "Der Vorlagenordner konnte nicht initialisiert werden",
"List of favorites files and folders." : "Liste der favorisierten Ordner und Dateien",
"No favorites yet" : "Noch keine Favoriten vorhanden",
"Files and folders you mark as favorite will show up here" : "Dateien und Ordner, die als Favoriten markiert werden, erscheinen hier",
"All files" : "Alle Dateien",
"List of your files and folders." : "Liste deiner Dateien und Ordner",
"List of recently modified files and folders." : "Liste der zuletzt geänderten Dateien und Ordner",
"No recently modified files" : "Keine kürzlich geänderten Dateien",
"Files and folders you recently modified will show up here." : "Die von dir kürzlich geänderten Dateien und Ordner werden hier angezeigt.",
"Toggle %1$s sublist" : "Unterliste %1$s umschalten",
"No entries found in this folder" : "Keine Einträge in diesem Ordner gefunden",
"Upload too large" : "Der Upload ist zu groß",
@ -254,7 +280,7 @@
"Deleted shares" : "Gelöschte Freigaben",
"Pending shares" : "Ausstehende Freigaben",
"Open folder {name}" : "Ordner {name} öffnen",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Diese Liste ist aus Performance-Gründen nicht vollständig gerendert. Die Dateien werden gerendert, wenn du durch die Liste navigierst.",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Diese Liste wird aus Performance-Gründen nicht vollständig angezeigt. Die Dateien werden gerendert, wenn du durch die Liste navigierst.",
"Search for an account" : "Nach einem Konto suchen",
"Choose" : "Auswählen",
"No files or folders have been deleted yet" : "Es wurden noch keine Dateien oder Ordner gelöscht"

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Es ist ein unbekannter Fehler aufgetreten",
"File could not be uploaded" : "Datei konnte nicht hochgeladen werden",
"Uploading …" : "Lade hoch …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Lade hoch… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} von {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Hochladen von Daten dieser Art wird nicht unterstützt.",
"Target folder does not exist any more" : "Zielordner existiert nicht mehr",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Es ist ein unbekannter Fehler aufgetreten",
"File could not be uploaded" : "Datei konnte nicht hochgeladen werden",
"Uploading …" : "Lade hoch …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Lade hoch… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} von {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Hochladen von Daten dieser Art wird nicht unterstützt.",
"Target folder does not exist any more" : "Zielordner existiert nicht mehr",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "An unknown error has occurred",
"File could not be uploaded" : "File could not be uploaded",
"Uploading …" : "Uploading …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Uploading … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} of {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Uploading that item is not supported",
"Target folder does not exist any more" : "Target folder does not exist any more",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "An unknown error has occurred",
"File could not be uploaded" : "File could not be uploaded",
"Uploading …" : "Uploading …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Uploading … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} of {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Uploading that item is not supported",
"Target folder does not exist any more" : "Target folder does not exist any more",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Ha ocurrido un error desconocido",
"File could not be uploaded" : "No se ha podido subir el archivo",
"Uploading …" : "Subiendo …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Cargando… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Subir este archivo no es compatible",
"Target folder does not exist any more" : "La carpeta destino ya no existe",
@ -32,6 +34,7 @@ OC.L10N.register(
"Move" : "Mover",
"Copy" : "Copiar",
"Choose target folder" : "Elegir carpeta de destino",
"Set reminder" : "Enviar recordatorio",
"Edit locally" : "Editar localmente",
"Open" : "Abrir",
"Delete file" : "Eliminar archivo",
@ -174,6 +177,7 @@ OC.L10N.register(
"Rename file" : "Renombrar archivo",
"File name" : "Nombre del archivo",
"A long time ago" : "Hace mucho tiempo",
"This node is unavailable" : "Este nodo no está disponible",
"Download file {name}" : "Descargar archivo {name}",
"\"{displayName}\" action executed successfully" : "la acción \"{displayName}\" se ejecutó exitósamente",
"\"{displayName}\" action failed" : "la acción \"{displayName}\" falló",
@ -251,6 +255,7 @@ OC.L10N.register(
"No favorites yet" : "Aún no hay favoritos",
"Files and folders you mark as favorite will show up here" : "Aquí aparecerán los archivos y carpetas que has marcado como favoritos",
"All files" : "Todos los archivos",
"List of your files and folders." : "Lista de sus archivos y carpetas.",
"List of recently modified files and folders." : "Lista de archivos y carpetas modificados recientemente.",
"No recently modified files" : "No hay archivos modificados recientemente.",
"Files and folders you recently modified will show up here." : "Los archivos y carpetas que ha modificado recientemente aparecerán aquí.",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Ha ocurrido un error desconocido",
"File could not be uploaded" : "No se ha podido subir el archivo",
"Uploading …" : "Subiendo …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Cargando… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Subir este archivo no es compatible",
"Target folder does not exist any more" : "La carpeta destino ya no existe",
@ -30,6 +32,7 @@
"Move" : "Mover",
"Copy" : "Copiar",
"Choose target folder" : "Elegir carpeta de destino",
"Set reminder" : "Enviar recordatorio",
"Edit locally" : "Editar localmente",
"Open" : "Abrir",
"Delete file" : "Eliminar archivo",
@ -172,6 +175,7 @@
"Rename file" : "Renombrar archivo",
"File name" : "Nombre del archivo",
"A long time ago" : "Hace mucho tiempo",
"This node is unavailable" : "Este nodo no está disponible",
"Download file {name}" : "Descargar archivo {name}",
"\"{displayName}\" action executed successfully" : "la acción \"{displayName}\" se ejecutó exitósamente",
"\"{displayName}\" action failed" : "la acción \"{displayName}\" falló",
@ -249,6 +253,7 @@
"No favorites yet" : "Aún no hay favoritos",
"Files and folders you mark as favorite will show up here" : "Aquí aparecerán los archivos y carpetas que has marcado como favoritos",
"All files" : "Todos los archivos",
"List of your files and folders." : "Lista de sus archivos y carpetas.",
"List of recently modified files and folders." : "Lista de archivos y carpetas modificados recientemente.",
"No recently modified files" : "No hay archivos modificados recientemente.",
"Files and folders you recently modified will show up here." : "Los archivos y carpetas que ha modificado recientemente aparecerán aquí.",

@ -22,6 +22,7 @@ OC.L10N.register(
"An unknown error has occurred" : "Tapahtui tuntematon virhe",
"File could not be uploaded" : "Tiedostoa ei voi lähettää",
"Uploading …" : "Lähetetään…",
"Uploading … ({currentNumber}/{total})" : "Lähetetään… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize}/{totalSize} ({bitrate})",
"Uploading that item is not supported" : "Kyseisen kohteen lähettäminen ei ole tuettu",
"Target folder does not exist any more" : "Kohdekansiota ei ole enää olemassa",
@ -244,6 +245,7 @@ OC.L10N.register(
"No favorites yet" : "Ei vielä suosikkeja",
"Files and folders you mark as favorite will show up here" : "Suosikeiksi merkitsemäsi tiedostot ja kansiot näkyvät täällä",
"All files" : "Kaikki tiedostot",
"List of your files and folders." : "Luettelo tiedostoistasi ja kansioistasi.",
"List of recently modified files and folders." : "Luettelo äskettäin muokatuista tiedostoista ja kansioista.",
"No recently modified files" : "Ei äskettäin muokattuja tiedostoja",
"Files and folders you recently modified will show up here." : "Äskettäin muokkaamasi tiedostot ja kansiot näkyvät täällä.",

@ -20,6 +20,7 @@
"An unknown error has occurred" : "Tapahtui tuntematon virhe",
"File could not be uploaded" : "Tiedostoa ei voi lähettää",
"Uploading …" : "Lähetetään…",
"Uploading … ({currentNumber}/{total})" : "Lähetetään… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize}/{totalSize} ({bitrate})",
"Uploading that item is not supported" : "Kyseisen kohteen lähettäminen ei ole tuettu",
"Target folder does not exist any more" : "Kohdekansiota ei ole enää olemassa",
@ -242,6 +243,7 @@
"No favorites yet" : "Ei vielä suosikkeja",
"Files and folders you mark as favorite will show up here" : "Suosikeiksi merkitsemäsi tiedostot ja kansiot näkyvät täällä",
"All files" : "Kaikki tiedostot",
"List of your files and folders." : "Luettelo tiedostoistasi ja kansioistasi.",
"List of recently modified files and folders." : "Luettelo äskettäin muokatuista tiedostoista ja kansioista.",
"No recently modified files" : "Ei äskettäin muokattuja tiedostoja",
"Files and folders you recently modified will show up here." : "Äskettäin muokkaamasi tiedostot ja kansiot näkyvät täällä.",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Une erreur inconnue est survenue",
"File could not be uploaded" : "Le fichier n'a pas pu être téléversé",
"Uploading …" : "Envoi en cours...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Téléversement en cours… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} sur {totalSize} ({bitrate})",
"Uploading that item is not supported" : "L'envoi de cet élément n'est pas supporté",
"Target folder does not exist any more" : "Le dossier cible n'existe plus",
@ -32,6 +34,7 @@ OC.L10N.register(
"Move" : "Déplacer",
"Copy" : "Copier",
"Choose target folder" : "Sélectionner le dossier cible",
"Set reminder" : "Définir un rappel",
"Edit locally" : "Éditer localement",
"Open" : "Ouvrir",
"Delete file" : "Supprimer le fichier",
@ -241,6 +244,7 @@ OC.L10N.register(
"Unable to create new file from template" : "Impossible de créer un nouveau fichier à partir du modèle",
"Delete permanently" : "Supprimer définitivement",
"Open folder {displayName}" : "Ouvrir le dossier {displayName}",
"Open in Files" : "Ouvrir dans Fichiers",
"Open details" : "Ouvrir les détails",
"Set up templates folder" : "Configurer le dossier des modèles",
"Templates" : "Modèles",
@ -250,6 +254,10 @@ OC.L10N.register(
"No favorites yet" : "Aucun favori pour l'instant",
"Files and folders you mark as favorite will show up here" : "Les fichiers et dossiers ajoutés à vos favoris apparaîtront ici",
"All files" : "Tous les fichiers",
"List of your files and folders." : "Liste de vos fichiers et dossiers.",
"List of recently modified files and folders." : "Liste des fichiers et dossiers récemment modifiés.",
"No recently modified files" : "Pas de fichiers récemment modifiés",
"Files and folders you recently modified will show up here." : "Les fichiers et dossiers que vous avez récemment modifiés apparaîtront ici.",
"Toggle %1$s sublist" : "Basculer %1$s sous-liste",
"No entries found in this folder" : "Aucune entrée trouvée dans ce dossier",
"Upload too large" : "Données envoyées trop volumineuses",
@ -275,7 +283,7 @@ OC.L10N.register(
"Open folder {name}" : "Ouvrir le dossier {name}",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Cette liste n'est pas affichée en totalité pour des raisons de performances. Les fichiers seront affichés au fur et à mesure que vous parcourrez la liste.",
"Search for an account" : "Chercher un compte",
"Choose" : "Sélectionner",
"Choose" : "Choisir",
"No files or folders have been deleted yet" : "Aucun fichier ou dossier n'a encore été supprimé"
},
"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Une erreur inconnue est survenue",
"File could not be uploaded" : "Le fichier n'a pas pu être téléversé",
"Uploading …" : "Envoi en cours...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Téléversement en cours… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} sur {totalSize} ({bitrate})",
"Uploading that item is not supported" : "L'envoi de cet élément n'est pas supporté",
"Target folder does not exist any more" : "Le dossier cible n'existe plus",
@ -30,6 +32,7 @@
"Move" : "Déplacer",
"Copy" : "Copier",
"Choose target folder" : "Sélectionner le dossier cible",
"Set reminder" : "Définir un rappel",
"Edit locally" : "Éditer localement",
"Open" : "Ouvrir",
"Delete file" : "Supprimer le fichier",
@ -239,6 +242,7 @@
"Unable to create new file from template" : "Impossible de créer un nouveau fichier à partir du modèle",
"Delete permanently" : "Supprimer définitivement",
"Open folder {displayName}" : "Ouvrir le dossier {displayName}",
"Open in Files" : "Ouvrir dans Fichiers",
"Open details" : "Ouvrir les détails",
"Set up templates folder" : "Configurer le dossier des modèles",
"Templates" : "Modèles",
@ -248,6 +252,10 @@
"No favorites yet" : "Aucun favori pour l'instant",
"Files and folders you mark as favorite will show up here" : "Les fichiers et dossiers ajoutés à vos favoris apparaîtront ici",
"All files" : "Tous les fichiers",
"List of your files and folders." : "Liste de vos fichiers et dossiers.",
"List of recently modified files and folders." : "Liste des fichiers et dossiers récemment modifiés.",
"No recently modified files" : "Pas de fichiers récemment modifiés",
"Files and folders you recently modified will show up here." : "Les fichiers et dossiers que vous avez récemment modifiés apparaîtront ici.",
"Toggle %1$s sublist" : "Basculer %1$s sous-liste",
"No entries found in this folder" : "Aucune entrée trouvée dans ce dossier",
"Upload too large" : "Données envoyées trop volumineuses",
@ -273,7 +281,7 @@
"Open folder {name}" : "Ouvrir le dossier {name}",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Cette liste n'est pas affichée en totalité pour des raisons de performances. Les fichiers seront affichés au fur et à mesure que vous parcourrez la liste.",
"Search for an account" : "Chercher un compte",
"Choose" : "Sélectionner",
"Choose" : "Choisir",
"No files or folders have been deleted yet" : "Aucun fichier ou dossier n'a encore été supprimé"
},"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
}

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Produciuse un erro descoñecido",
"File could not be uploaded" : "Non foi posíbel enviar o ficheiro",
"Uploading …" : "Enviando…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Enviando… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Non se admite o envío deste elemento",
"Target folder does not exist any more" : "O cartafol de destino xa non existe",
@ -280,7 +282,7 @@ OC.L10N.register(
"Deleted shares" : "Comparticións eliminadas",
"Pending shares" : "Comparticións pendentes",
"Open folder {name}" : "Abrir o cartafol {name}",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Esta lista non se representa de xeito completo por mor do rendemento. Os ficheiros represéntanse mentres navega pola lista.",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Esta lista non se representa de xeito completo por mor do rendemento. Os ficheiros represéntanse se despraza pola lista.",
"Search for an account" : "Buscar por unha conta",
"Choose" : "Escoller",
"No files or folders have been deleted yet" : "Aínda non se eliminou ningún ficheiro nin cartafol"

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Produciuse un erro descoñecido",
"File could not be uploaded" : "Non foi posíbel enviar o ficheiro",
"Uploading …" : "Enviando…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Enviando… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Non se admite o envío deste elemento",
"Target folder does not exist any more" : "O cartafol de destino xa non existe",
@ -278,7 +280,7 @@
"Deleted shares" : "Comparticións eliminadas",
"Pending shares" : "Comparticións pendentes",
"Open folder {name}" : "Abrir o cartafol {name}",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Esta lista non se representa de xeito completo por mor do rendemento. Os ficheiros represéntanse mentres navega pola lista.",
"This list is not fully rendered for performances reasons. The files will be rendered as you navigate through the list." : "Esta lista non se representa de xeito completo por mor do rendemento. Os ficheiros represéntanse se despraza pola lista.",
"Search for an account" : "Buscar por unha conta",
"Choose" : "Escoller",
"No files or folders have been deleted yet" : "Aínda non se eliminou ningún ficheiro nin cartafol"

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Um erro desconhecido ocorreu",
"File could not be uploaded" : "Não foi possível enviar o arquivo",
"Uploading …" : "Enviando...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Enviando … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "O envio deste item não é suportado",
"Target folder does not exist any more" : "Pasta destino não existe mais",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Um erro desconhecido ocorreu",
"File could not be uploaded" : "Não foi possível enviar o arquivo",
"Uploading …" : "Enviando...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Enviando … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})",
"Uploading that item is not supported" : "O envio deste item não é suportado",
"Target folder does not exist any more" : "Pasta destino não existe mais",

@ -228,7 +228,7 @@ OC.L10N.register(
"Shared with others" : "Sprístupnené ostatným",
"Shared with you" : "Vám sprístupnené",
"Shared by link" : "Sprístupnené prostredníctvom odkazu",
"Deleted shares" : "Vymazané zdieľania",
"Deleted shares" : "Zmazané sprístupnenia",
"Pending shares" : "Čakajúce prístupy",
"Choose" : "Vybrať"
},

@ -226,7 +226,7 @@
"Shared with others" : "Sprístupnené ostatným",
"Shared with you" : "Vám sprístupnené",
"Shared by link" : "Sprístupnené prostredníctvom odkazu",
"Deleted shares" : "Vymazané zdieľania",
"Deleted shares" : "Zmazané sprístupnenia",
"Pending shares" : "Čakajúce prístupy",
"Choose" : "Vybrať"
},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Десила се непозната грешка",
"File could not be uploaded" : "Фајл не може да се отпреми",
"Uploading …" : "Отпремам…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Отпремање … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} од {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Отпремање те ставке није подржано",
"Target folder does not exist any more" : "Одредишна фасцикла више не постоји",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Десила се непозната грешка",
"File could not be uploaded" : "Фајл не може да се отпреми",
"Uploading …" : "Отпремам…",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Отпремање … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} од {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Отпремање те ставке није подржано",
"Target folder does not exist any more" : "Одредишна фасцикла више не постоји",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Ett okänt fel uppstod",
"File could not be uploaded" : "Filen kunde inte laddas upp",
"Uploading …" : "Laddar upp ..",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Laddar upp … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} av {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Uppladdning av det här objektet stöds inte",
"Target folder does not exist any more" : "Målmapp existerar inte längre",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Ett okänt fel uppstod",
"File could not be uploaded" : "Filen kunde inte laddas upp",
"Uploading …" : "Laddar upp ..",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Laddar upp … ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} av {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Uppladdning av det här objektet stöds inte",
"Target folder does not exist any more" : "Målmapp existerar inte längre",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "Bilinmeyen bir sorun çıktı",
"File could not be uploaded" : "Dosya yüklenemedi",
"Uploading …" : "Yükleniyor …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Yükleniyor… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} / {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Bu ögenin yüklenmesi desteklenmiyor",
"Target folder does not exist any more" : "Hedef klasör artık yok",
@ -175,6 +177,7 @@ OC.L10N.register(
"Rename file" : "Dosyayı yeniden adlandır",
"File name" : "Dosya adı",
"A long time ago" : "Uzun süre önce",
"This node is unavailable" : "Bu düğüm kullanılamıyor",
"Download file {name}" : "{name} dosyasını indir",
"\"{displayName}\" action executed successfully" : "\"{displayName}\" işlemi tamamlandı",
"\"{displayName}\" action failed" : "\"{displayName}\" işlemi tamamlanamadı",
@ -194,7 +197,7 @@ OC.L10N.register(
"Sort list by {column} ({direction})" : "Listeyi {column} sütununa göre sırala ({direction})",
"Select all" : "Tümünü seç",
"Unselect all" : "Tümünü bırak",
"List of files and folders." : "Doys ave klasörleri listesi.",
"List of files and folders." : "Dosya ve klasörlerin listesi.",
"This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Başarımı olumsuz etkilememek için listenin tümü görüntülenmiyor. Listede ilerledikçe dosyalar görüntülenecek.",
"File not found" : "Dosya bulunamadı",
"Storage informations" : "Depolama bilgileri",
@ -252,6 +255,7 @@ OC.L10N.register(
"No favorites yet" : "Henüz sık kullanılan bir öge yok",
"Files and folders you mark as favorite will show up here" : "Sık kullanılanlara eklediğiniz dosya ve klasörler burada görüntülenir",
"All files" : "Tüm dosyalar",
"List of your files and folders." : "Dosya ve klasörlerinizin listesi",
"List of recently modified files and folders." : "Son değiştirilen dosya ve klasörlerin listesi.",
"No recently modified files" : "Yakınlarda değiştirilmiş bir dosya yok",
"Files and folders you recently modified will show up here." : "Son zamanlarda değiştirdiğiniz dosya ve klasörler burada görüntülenir.",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "Bilinmeyen bir sorun çıktı",
"File could not be uploaded" : "Dosya yüklenemedi",
"Uploading …" : "Yükleniyor …",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "Yükleniyor… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} / {totalSize} ({bitrate})",
"Uploading that item is not supported" : "Bu ögenin yüklenmesi desteklenmiyor",
"Target folder does not exist any more" : "Hedef klasör artık yok",
@ -173,6 +175,7 @@
"Rename file" : "Dosyayı yeniden adlandır",
"File name" : "Dosya adı",
"A long time ago" : "Uzun süre önce",
"This node is unavailable" : "Bu düğüm kullanılamıyor",
"Download file {name}" : "{name} dosyasını indir",
"\"{displayName}\" action executed successfully" : "\"{displayName}\" işlemi tamamlandı",
"\"{displayName}\" action failed" : "\"{displayName}\" işlemi tamamlanamadı",
@ -192,7 +195,7 @@
"Sort list by {column} ({direction})" : "Listeyi {column} sütununa göre sırala ({direction})",
"Select all" : "Tümünü seç",
"Unselect all" : "Tümünü bırak",
"List of files and folders." : "Doys ave klasörleri listesi.",
"List of files and folders." : "Dosya ve klasörlerin listesi.",
"This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Başarımı olumsuz etkilememek için listenin tümü görüntülenmiyor. Listede ilerledikçe dosyalar görüntülenecek.",
"File not found" : "Dosya bulunamadı",
"Storage informations" : "Depolama bilgileri",
@ -250,6 +253,7 @@
"No favorites yet" : "Henüz sık kullanılan bir öge yok",
"Files and folders you mark as favorite will show up here" : "Sık kullanılanlara eklediğiniz dosya ve klasörler burada görüntülenir",
"All files" : "Tüm dosyalar",
"List of your files and folders." : "Dosya ve klasörlerinizin listesi",
"List of recently modified files and folders." : "Son değiştirilen dosya ve klasörlerin listesi.",
"No recently modified files" : "Yakınlarda değiştirilmiş bir dosya yok",
"Files and folders you recently modified will show up here." : "Son zamanlarda değiştirdiğiniz dosya ve klasörler burada görüntülenir.",

@ -159,14 +159,14 @@ OC.L10N.register(
"Upload (max. %s)" : "Завантаження (макс. %s)",
"Accept" : "Прийняти",
"Reject" : "Відхилити",
"Incoming ownership transfer from {user}" : "Запит від користувача {user} на передачу прав власності",
"Do you want to accept {path}?\n\nNote: The transfer process after accepting may take up to 1 hour." : "Прийняти {path}?\n\nУвага: процес передачі після прийняття може тривати до 1 години.",
"Incoming ownership transfer from {user}" : "Запит від користувача {user} на передавання прав власника",
"Do you want to accept {path}?\n\nNote: The transfer process after accepting may take up to 1 hour." : "Прийняти {path}?\n\nУвага: Процес передавання після прийняття запиту може тривати до 1 години.",
"Ownership transfer failed" : "Не вдалося передати право власності",
"Your ownership transfer of {path} to {user} failed." : "Передача вашого права власності на {path} користувачеві {user} не вдалася.",
"The ownership transfer of {path} from {user} failed." : "Передача прав власника на {path} від користувача {user} не вдалася.",
"Your ownership transfer of {path} to {user} failed." : "Не вдалося перадати ваші права власника на {path} користувачеві {user}.",
"The ownership transfer of {path} from {user} failed." : "Не вдалося передати права власника на {path} від користувача {user}.",
"Ownership transfer done" : "Право власності передано",
"Your ownership transfer of {path} to {user} has completed." : "Передачу ваших прав власника на {path} користувачеві {user} виконано.",
"The ownership transfer of {path} from {user} has completed." : "Передачу прав власника на {path} від користувача {user} завершено.",
"Your ownership transfer of {path} to {user} has completed." : "Ваші права власника на {path} успішно передано користувачеві {user}.",
"The ownership transfer of {path} from {user} has completed." : "Завершено передавання права власника на {path} від користувача {user}.",
"in %s" : "в %s",
"File Management" : "Керування файлами",
"Reload current directory" : "Перевантажити поточний каталог",
@ -201,16 +201,16 @@ OC.L10N.register(
"{usedQuotaByte} used" : "{usedQuotaByte} використано",
"{relative}% used" : "{relative}% використано",
"Could not refresh storage stats" : "Неможливо оновити статистику сховища",
"Transfer ownership of a file or folder" : "Передача прав власника на файл або каталог",
"Choose file or folder to transfer" : "Виберіть файл або каталог для передачі",
"Transfer ownership of a file or folder" : "Передавання прав власника на файл або каталог",
"Choose file or folder to transfer" : "Виберіть файл або каталог для передавання",
"Change" : "Змінити",
"New owner" : "Новий власник",
"Choose a file or folder to transfer" : "Виберіть файл або каталог для передачі",
"Choose a file or folder to transfer" : "Виберіть файл або каталог для передавання",
"Transfer" : "Передати",
"Transfer {path} to {userid}" : "Передати {path} користувачеві {userid}",
"Invalid path selected" : "Вибрано неправильний шлях",
"Unknown error" : "Невідома помилка",
"Ownership transfer request sent" : "Запит на передачу прав власності надіслано",
"Ownership transfer request sent" : "Запит на передавання прав власника надіслано",
"Cannot transfer ownership of a file or folder you do not own" : "Неможливо передати права власності на файл або каталог, якими ви не володієте",
"Select file or folder to link to" : "Виберіть файл або каталог, до якого потрібно застосувати посилання",
"Loading current folder" : "Завантаження поточного каталогу",

@ -157,14 +157,14 @@
"Upload (max. %s)" : "Завантаження (макс. %s)",
"Accept" : "Прийняти",
"Reject" : "Відхилити",
"Incoming ownership transfer from {user}" : "Запит від користувача {user} на передачу прав власності",
"Do you want to accept {path}?\n\nNote: The transfer process after accepting may take up to 1 hour." : "Прийняти {path}?\n\nУвага: процес передачі після прийняття може тривати до 1 години.",
"Incoming ownership transfer from {user}" : "Запит від користувача {user} на передавання прав власника",
"Do you want to accept {path}?\n\nNote: The transfer process after accepting may take up to 1 hour." : "Прийняти {path}?\n\nУвага: Процес передавання після прийняття запиту може тривати до 1 години.",
"Ownership transfer failed" : "Не вдалося передати право власності",
"Your ownership transfer of {path} to {user} failed." : "Передача вашого права власності на {path} користувачеві {user} не вдалася.",
"The ownership transfer of {path} from {user} failed." : "Передача прав власника на {path} від користувача {user} не вдалася.",
"Your ownership transfer of {path} to {user} failed." : "Не вдалося перадати ваші права власника на {path} користувачеві {user}.",
"The ownership transfer of {path} from {user} failed." : "Не вдалося передати права власника на {path} від користувача {user}.",
"Ownership transfer done" : "Право власності передано",
"Your ownership transfer of {path} to {user} has completed." : "Передачу ваших прав власника на {path} користувачеві {user} виконано.",
"The ownership transfer of {path} from {user} has completed." : "Передачу прав власника на {path} від користувача {user} завершено.",
"Your ownership transfer of {path} to {user} has completed." : "Ваші права власника на {path} успішно передано користувачеві {user}.",
"The ownership transfer of {path} from {user} has completed." : "Завершено передавання права власника на {path} від користувача {user}.",
"in %s" : "в %s",
"File Management" : "Керування файлами",
"Reload current directory" : "Перевантажити поточний каталог",
@ -199,16 +199,16 @@
"{usedQuotaByte} used" : "{usedQuotaByte} використано",
"{relative}% used" : "{relative}% використано",
"Could not refresh storage stats" : "Неможливо оновити статистику сховища",
"Transfer ownership of a file or folder" : "Передача прав власника на файл або каталог",
"Choose file or folder to transfer" : "Виберіть файл або каталог для передачі",
"Transfer ownership of a file or folder" : "Передавання прав власника на файл або каталог",
"Choose file or folder to transfer" : "Виберіть файл або каталог для передавання",
"Change" : "Змінити",
"New owner" : "Новий власник",
"Choose a file or folder to transfer" : "Виберіть файл або каталог для передачі",
"Choose a file or folder to transfer" : "Виберіть файл або каталог для передавання",
"Transfer" : "Передати",
"Transfer {path} to {userid}" : "Передати {path} користувачеві {userid}",
"Invalid path selected" : "Вибрано неправильний шлях",
"Unknown error" : "Невідома помилка",
"Ownership transfer request sent" : "Запит на передачу прав власності надіслано",
"Ownership transfer request sent" : "Запит на передавання прав власника надіслано",
"Cannot transfer ownership of a file or folder you do not own" : "Неможливо передати права власності на файл або каталог, якими ви не володієте",
"Select file or folder to link to" : "Виберіть файл або каталог, до якого потрібно застосувати посилання",
"Loading current folder" : "Завантаження поточного каталогу",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "發生了未知的錯誤",
"File could not be uploaded" : "檔案無法上傳",
"Uploading …" : "上傳中...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "上傳中 ... {currentNumber}{total}",
"{loadedSize} of {totalSize} ({bitrate})" : "{totalSize} 中的 {loadedSize}{bitrate}",
"Uploading that item is not supported" : "不支援上傳該項目",
"Target folder does not exist any more" : "目標資料夾已經不存在了",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "發生了未知的錯誤",
"File could not be uploaded" : "檔案無法上傳",
"Uploading …" : "上傳中...",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "上傳中 ... {currentNumber}{total}",
"{loadedSize} of {totalSize} ({bitrate})" : "{totalSize} 中的 {loadedSize}{bitrate}",
"Uploading that item is not supported" : "不支援上傳該項目",
"Target folder does not exist any more" : "目標資料夾已經不存在了",

@ -22,6 +22,8 @@ OC.L10N.register(
"An unknown error has occurred" : "發生未知的錯誤",
"File could not be uploaded" : "檔案無法上傳",
"Uploading …" : "正在上傳……",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "正在上傳…… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{totalSize} 中的 {loadedSize} ({bitrate})",
"Uploading that item is not supported" : "不支援上傳該項目",
"Target folder does not exist any more" : "目標資料夾已經不存在了",

@ -20,6 +20,8 @@
"An unknown error has occurred" : "發生未知的錯誤",
"File could not be uploaded" : "檔案無法上傳",
"Uploading …" : "正在上傳……",
"{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})",
"Uploading … ({currentNumber}/{total})" : "正在上傳…… ({currentNumber}/{total})",
"{loadedSize} of {totalSize} ({bitrate})" : "{totalSize} 中的 {loadedSize} ({bitrate})",
"Uploading that item is not supported" : "不支援上傳該項目",
"Target folder does not exist any more" : "目標資料夾已經不存在了",

@ -22,12 +22,12 @@
"schemas": {
"Capabilities": {
"type": "object",
"required": [
"files"
],
"properties": {
"files": {
"type": [
"object",
"object"
],
"type": "object",
"required": [
"bigfilechunking",
"blacklisted_files",

@ -21,22 +21,20 @@
*/
import { action } from './deleteAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission } from '@nextcloud/files'
import { FileAction } from '../services/FileAction'
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
import * as eventBus from '@nextcloud/event-bus'
import axios from '@nextcloud/axios'
import logger from '../logger'
import type { Navigation } from '../services/Navigation'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
const trashbinView = {
id: 'trashbin',
name: 'Trashbin',
} as Navigation
} as View
describe('Delete action conditions tests', () => {
test('Default values', () => {

@ -20,18 +20,16 @@
*
*/
import { emit } from '@nextcloud/event-bus'
import { Permission, Node } from '@nextcloud/files'
import { Permission, Node, View, registerFileAction, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw'
import { registerFileAction, FileAction } from '../services/FileAction'
import logger from '../logger.js'
import type { Navigation } from '../services/Navigation'
export const action = new FileAction({
id: 'delete',
displayName(nodes: Node[], view: Navigation) {
displayName(nodes: Node[], view: View) {
return view.id === 'trashbin'
? t('files_trashbin', 'Delete permanently')
: t('files', 'Delete')
@ -58,7 +56,7 @@ export const action = new FileAction({
return false
}
},
async execBatch(nodes: Node[], view: Navigation, dir: string) {
async execBatch(nodes: Node[], view: View, dir: string) {
return Promise.all(nodes.map(node => this.exec(node, view, dir)))
},

@ -21,17 +21,12 @@
*/
import { action } from './downloadAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission } from '@nextcloud/files'
import { FileAction } from '../services/FileAction'
import * as eventBus from '@nextcloud/event-bus'
import axios from '@nextcloud/axios'
import type { Navigation } from '../services/Navigation'
import logger from '../logger'
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
describe('Download action conditions tests', () => {
test('Default values', () => {

@ -19,14 +19,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { Permission, Node, FileType } from '@nextcloud/files'
import { generateUrl } from '@nextcloud/router'
import { registerFileAction, FileAction, Permission, Node, FileType, View } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
import { registerFileAction, FileAction, DefaultType } from '../services/FileAction'
import { generateUrl } from '@nextcloud/router'
import type { Navigation } from '../services/Navigation'
const triggerDownload = function(url: string) {
const hiddenElement = document.createElement('a')
hiddenElement.download = ''
@ -55,7 +52,7 @@ export const action = new FileAction({
.every(permission => (permission & Permission.READ) !== 0)
},
async exec(node: Node, view: Navigation, dir: string) {
async exec(node: Node, view: View, dir: string) {
if (node.type === FileType.Folder) {
downloadNodes(dir, [node])
return null
@ -65,7 +62,7 @@ export const action = new FileAction({
return null
},
async execBatch(nodes: Node[], view: Navigation, dir: string) {
async execBatch(nodes: Node[], view: View, dir: string) {
if (nodes.length === 1) {
this.exec(nodes[0], view, dir)
return [null]

@ -21,16 +21,14 @@
*/
import { action } from './editLocallyAction'
import { expect } from '@jest/globals'
import { File, Permission } from '@nextcloud/files'
import { DefaultType, FileAction } from '../services/FileAction'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import * as ncDialogs from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import type { Navigation } from '../services/Navigation'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
describe('Edit locally action conditions tests', () => {
test('Default values', () => {

@ -20,15 +20,14 @@
*
*/
import { encodePath } from '@nextcloud/paths'
import { Permission, type Node } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import LaptopSvg from '@mdi/svg/svg/laptop.svg?raw'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { registerFileAction, FileAction, DefaultType } from '../services/FileAction'
import { registerFileAction, FileAction, Permission, type Node } from '@nextcloud/files'
import { showError } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import LaptopSvg from '@mdi/svg/svg/laptop.svg?raw'
const openLocalClient = async function(path: string) {
const link = generateOcsUrl('apps/files/api/v1') + '/openlocaleditor?format=json'

@ -19,25 +19,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import * as favoriteAction from './favoriteAction'
import { action } from './favoriteAction'
import { expect } from '@jest/globals'
import { File, Permission } from '@nextcloud/files'
import { FileAction } from '../services/FileAction'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import * as eventBus from '@nextcloud/event-bus'
import * as favoriteAction from './favoriteAction'
import axios from '@nextcloud/axios'
import type { Navigation } from '../services/Navigation'
import logger from '../logger'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
const favoriteView = {
id: 'favorites',
name: 'Favorites',
} as Navigation
} as View
global.window.OC = {
TAG_FAVORITE: '_$!<Favorite>!$_',

@ -21,7 +21,7 @@
*/
import { emit } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'
import { Permission, type Node } from '@nextcloud/files'
import { Permission, type Node, View, registerFileAction, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import Vue from 'vue'
@ -29,16 +29,14 @@ import Vue from 'vue'
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
import StarSvg from '@mdi/svg/svg/star.svg?raw'
import { registerFileAction, FileAction } from '../services/FileAction'
import logger from '../logger.js'
import type { Navigation } from '../services/Navigation'
// If any of the nodes is not favorited, we display the favorite action.
const shouldFavorite = (nodes: Node[]): boolean => {
return nodes.some(node => node.attributes.favorite !== 1)
}
export const favoriteNode = async (node: Node, view: Navigation, willFavorite: boolean): Promise<boolean> => {
export const favoriteNode = async (node: Node, view: View, willFavorite: boolean): Promise<boolean> => {
try {
// TODO: migrate to webdav tags plugin
const url = generateUrl('/apps/files/api/v1/files') + node.path
@ -92,11 +90,11 @@ export const action = new FileAction({
&& nodes.every(node => node.permissions !== Permission.NONE)
},
async exec(node: Node, view: Navigation) {
async exec(node: Node, view: View) {
const willFavorite = shouldFavorite([node])
return await favoriteNode(node, view, willFavorite)
},
async execBatch(nodes: Node[], view: Navigation) {
async execBatch(nodes: Node[], view: View) {
const willFavorite = shouldFavorite(nodes)
return Promise.all(nodes.map(async node => await favoriteNode(node, view, willFavorite)))
},

@ -19,18 +19,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { Navigation } from '../services/Navigation'
import { expect } from '@jest/globals'
import { File, Folder, Node, Permission } from '@nextcloud/files'
import { File, Folder, Node, Permission, View, DefaultType, FileAction } from '@nextcloud/files'
import { action } from './openFolderAction'
import { DefaultType, FileAction } from '../services/FileAction'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
describe('Open folder action conditions tests', () => {
test('Default values', () => {

@ -19,14 +19,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { Permission, Node, FileType } from '@nextcloud/files'
import { join } from 'path'
import { Permission, Node, FileType, View, registerFileAction, FileAction, DefaultType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
import type { Navigation } from '../services/Navigation'
import { join } from 'path'
import { registerFileAction, FileAction, DefaultType } from '../services/FileAction'
export const action = new FileAction({
id: 'open-folder',
displayName(files: Node[]) {
@ -52,7 +49,7 @@ export const action = new FileAction({
&& (node.permissions & Permission.READ) !== 0
},
async exec(node: Node, view: Navigation, dir: string) {
async exec(node: Node, view: View, dir: string) {
if (!node || node.type !== FileType.Folder) {
return false
}

@ -21,19 +21,17 @@
*/
import { action } from './openInFilesAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission } from '@nextcloud/files'
import { DefaultType, FileAction } from '../../../files/src/services/FileAction'
import type { Navigation } from '../../../files/src/services/Navigation'
import { File, Folder, Permission, View, DefaultType, FileAction } from '@nextcloud/files'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
const recentView = {
id: 'recent',
name: 'Recent',
} as Navigation
} as View
describe('Open in files action conditions tests', () => {
test('Default values', () => {

@ -22,7 +22,7 @@
import { translate as t } from '@nextcloud/l10n'
import { FileType, type Node } from '@nextcloud/files'
import { registerFileAction, FileAction, DefaultType } from '../../../files/src/services/FileAction'
import { registerFileAction, FileAction, DefaultType } from '@nextcloud/files'
/**
* TODO: Move away from a redirect and handle

@ -21,15 +21,13 @@
*/
import { action } from './renameAction'
import { expect } from '@jest/globals'
import { File, Permission } from '@nextcloud/files'
import { FileAction } from '../services/FileAction'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import * as eventBus from '@nextcloud/event-bus'
import type { Navigation } from '../services/Navigation'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
describe('Rename action conditions tests', () => {
test('Default values', () => {

@ -19,13 +19,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { Permission, type Node } from '@nextcloud/files'
import { emit } from '@nextcloud/event-bus'
import { Permission, type Node, registerFileAction, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import PencilSvg from '@mdi/svg/svg/pencil.svg?raw'
import { emit } from '@nextcloud/event-bus'
import { registerFileAction, FileAction } from '../services/FileAction'
export const ACTION_DETAILS = 'details'
export const action = new FileAction({

@ -19,19 +19,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { Navigation } from '../services/Navigation'
import { expect } from '@jest/globals'
import { File, Permission } from '@nextcloud/files'
import { File, Permission, View, FileAction } from '@nextcloud/files'
import { action } from './sidebarAction'
import { FileAction } from '../services/FileAction'
import logger from '../logger'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
describe('Open sidebar action conditions tests', () => {
test('Default values', () => {

@ -19,13 +19,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { Navigation } from '../services/Navigation'
import { Permission, type Node } from '@nextcloud/files'
import { Permission, type Node, View, registerFileAction, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
import { registerFileAction, FileAction } from '../services/FileAction'
import logger from '../logger.js'
export const ACTION_DETAILS = 'details'
@ -54,7 +51,7 @@ export const action = new FileAction({
return (nodes[0].root?.startsWith('/files/') && nodes[0].permissions !== Permission.NONE) ?? false
},
async exec(node: Node, view: Navigation) {
async exec(node: Node, view: View) {
try {
// TODO: migrate Sidebar to use a Node instead
await window.OCA.Files.Sidebar.open(node.path)

@ -21,14 +21,12 @@
*/
import { action } from './viewInFolderAction'
import { expect } from '@jest/globals'
import { File, Folder, Node, Permission } from '@nextcloud/files'
import { FileAction } from '../services/FileAction'
import type { Navigation } from '../services/Navigation'
import { File, Folder, Node, Permission, View, FileAction } from '@nextcloud/files'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
describe('View in folder action conditions tests', () => {
test('Default values', () => {

@ -19,14 +19,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { Node, FileType, Permission } from '@nextcloud/files'
import { Node, FileType, Permission, View, registerFileAction, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import FolderMoveSvg from '@mdi/svg/svg/folder-move.svg?raw'
import type { Navigation } from '../services/Navigation'
import { join } from 'path'
import { registerFileAction, FileAction } from '../services/FileAction'
export const action = new FileAction({
id: 'view-in-folder',
displayName() {
@ -53,7 +49,7 @@ export const action = new FileAction({
return node.type === FileType.File
},
async exec(node: Node, view: Navigation, dir: string) {
async exec(node: Node, view: View, dir: string) {
if (!node || node.type !== FileType.File) {
return false
}

@ -170,8 +170,8 @@ import { CancelablePromise } from 'cancelable-promise'
import { debounce } from 'debounce'
import { emit } from '@nextcloud/event-bus'
import { extname } from 'path'
import { formatFileSize, FileType, Permission } from '@nextcloud/files'
import { generateUrl } from '@nextcloud/router'
import { getFileActions, DefaultType, FileType, formatFileSize, Permission } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
import { vOnClickOutside } from '@vueuse/components'
@ -187,7 +187,6 @@ import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import Vue from 'vue'
import { ACTION_DETAILS } from '../actions/sidebarAction.ts'
import { getFileActions, DefaultType } from '../services/FileAction.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { isCachedPreview } from '../services/PreviewService.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'

@ -57,9 +57,9 @@
</template>
<script lang="ts">
import Vue from 'vue'
import { formatFileSize } from '@nextcloud/files'
import { translate } from '@nextcloud/l10n'
import Vue from 'vue'
import { useFilesStore } from '../store/files.ts'
import { usePathsStore } from '../store/paths.ts'

@ -50,7 +50,6 @@ export default {
},
computed: {
enabled() {
console.debug('Enabled', this.header.id)
return this.header.enabled(this.currentFolder, this.currentView)
},
},

@ -49,8 +49,7 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import Vue from 'vue'
import { getFileActions } from '../services/FileAction.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
import { getFileActions, useActionsMenuStore } from '../store/actionsmenu.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import filesListWidthMixin from '../mixins/filesListWidth.ts'

@ -42,6 +42,7 @@
</template>
<script lang="ts">
import { getFileActions } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
@ -49,7 +50,6 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import Vue from 'vue'
import { getFileActions } from '../services/FileAction.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'

@ -19,11 +19,12 @@
</template>
<script>
import { debounce, throttle } from 'throttle-debounce'
import { formatFileSize } from '@nextcloud/files'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import { debounce, throttle } from 'throttle-debounce'
import { subscribe } from '@nextcloud/event-bus'
import { translate } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import ChartPie from 'vue-material-design-icons/ChartPie.vue'
@ -31,7 +32,6 @@ import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationI
import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'
import logger from '../logger.js'
import { subscribe } from '@nextcloud/event-bus'
export default {
name: 'NavigationQuota',

@ -71,7 +71,6 @@ import logger from '../logger.js'
const picker = getFilePickerBuilder(t('files', 'Choose a file or folder to transfer'))
.setMultiSelect(false)
.setModal(true)
.setType(1)
.allowDirectories()
.build()

@ -32,8 +32,7 @@
</template>
<script lang="ts">
import { File, Folder } from '@nextcloud/files'
import { debounce } from 'debounce'
import { File, Folder, debounce } from 'debounce'
import Vue from 'vue'
import logger from '../logger.js'

@ -13,9 +13,9 @@ import './actions/viewInFolderAction'
import Vue from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
import { getNavigation } from '@nextcloud/files'
import FilesListView from './views/FilesList.vue'
import { NavigationService } from './services/Navigation'
import NavigationView from './views/Navigation.vue'
import registerFavoritesView from './views/favorites'
import registerRecentView from './views/recent'
@ -47,8 +47,7 @@ Vue.use(PiniaVuePlugin)
const pinia = createPinia()
// Init Navigation Service
const Navigation = new NavigationService()
Object.assign(window.OCP.Files, { Navigation })
const Navigation = getNavigation()
Vue.prototype.$navigation = Navigation
// Init Files App Settings Service

@ -23,14 +23,14 @@ import Vue from 'vue'
import { mapState } from 'pinia'
import { useViewConfigStore } from '../store/viewConfig'
import type { NavigationService, Navigation } from '../services/Navigation'
import { Navigation, View } from '@nextcloud/files'
export default Vue.extend({
computed: {
...mapState(useViewConfigStore, ['getConfig', 'setSortingBy', 'toggleSortingDirection']),
currentView(): Navigation {
return (this.$navigation as NavigationService).active as Navigation
currentView(): View {
return (this.$navigation as Navigation).active as View
},
/**

@ -19,13 +19,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { ContentsWithRoot } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed, DAVResultResponseProps } from 'webdav'
import { File, Folder, davParsePermissions } from '@nextcloud/files'
import { generateRemoteUrl } from '@nextcloud/router'
import { getClient, rootPath } from './WebdavClient'
import { getCurrentUser } from '@nextcloud/auth'
import { getClient, rootPath } from './WebdavClient'
import { getDavNameSpaces, getDavProperties, getDefaultPropfind } from './DavProperties'
import type { ContentsWithRoot } from './Navigation'
import type { FileStat, ResponseDataDetailed, DAVResultResponseProps } from 'webdav'
const client = getClient()

@ -1,191 +0,0 @@
/**
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { Node } from '@nextcloud/files'
import logger from '../logger'
import type { Navigation } from './Navigation'
declare global {
interface Window {
OC: any;
_nc_fileactions: FileAction[] | undefined;
}
}
export enum DefaultType {
DEFAULT = 'default',
HIDDEN = 'hidden',
}
/**
* TODO: remove and move to @nextcloud/files
* @see https://github.com/nextcloud/nextcloud-files/pull/608
*/
interface FileActionData {
/** Unique ID */
id: string
/** Translatable string displayed in the menu */
displayName: (files: Node[], view: Navigation) => string
/** Svg as inline string. <svg><path fill="..." /></svg> */
iconSvgInline: (files: Node[], view: Navigation) => string
/** Condition wether this action is shown or not */
enabled?: (files: Node[], view: Navigation) => boolean
/**
* Function executed on single file action
* @returns true if the action was executed, false otherwise
* @throws Error if the action failed
*/
exec: (file: Node, view: Navigation, dir: string) => Promise<boolean|null>,
/**
* Function executed on multiple files action
* @returns true if the action was executed successfully,
* false otherwise and null if the action is silent/undefined.
* @throws Error if the action failed
*/
execBatch?: (files: Node[], view: Navigation, dir: string) => Promise<(boolean|null)[]>
/** This action order in the list */
order?: number,
/** Make this action the default */
default?: DefaultType,
/**
* If true, the renderInline function will be called
*/
inline?: (file: Node, view: Navigation) => boolean,
/**
* If defined, the returned html element will be
* appended before the actions menu.
*/
renderInline?: (file: Node, view: Navigation) => Promise<HTMLElement | null>,
}
export class FileAction {
private _action: FileActionData
constructor(action: FileActionData) {
this.validateAction(action)
this._action = action
}
get id() {
return this._action.id
}
get displayName() {
return this._action.displayName
}
get iconSvgInline() {
return this._action.iconSvgInline
}
get enabled() {
return this._action.enabled
}
get exec() {
return this._action.exec
}
get execBatch() {
return this._action.execBatch
}
get order() {
return this._action.order
}
get default() {
return this._action.default
}
get inline() {
return this._action.inline
}
get renderInline() {
return this._action.renderInline
}
private validateAction(action: FileActionData) {
if (!action.id || typeof action.id !== 'string') {
throw new Error('Invalid id')
}
if (!action.displayName || typeof action.displayName !== 'function') {
throw new Error('Invalid displayName function')
}
if (!action.iconSvgInline || typeof action.iconSvgInline !== 'function') {
throw new Error('Invalid iconSvgInline function')
}
if (!action.exec || typeof action.exec !== 'function') {
throw new Error('Invalid exec function')
}
// Optional properties --------------------------------------------
if ('enabled' in action && typeof action.enabled !== 'function') {
throw new Error('Invalid enabled function')
}
if ('execBatch' in action && typeof action.execBatch !== 'function') {
throw new Error('Invalid execBatch function')
}
if ('order' in action && typeof action.order !== 'number') {
throw new Error('Invalid order')
}
if (action.default && !Object.values(DefaultType).includes(action.default)) {
throw new Error('Invalid default')
}
if ('inline' in action && typeof action.inline !== 'function') {
throw new Error('Invalid inline function')
}
if ('renderInline' in action && typeof action.renderInline !== 'function') {
throw new Error('Invalid renderInline function')
}
}
}
export const registerFileAction = function(action: FileAction): void {
if (typeof window._nc_fileactions === 'undefined') {
window._nc_fileactions = []
logger.debug('FileActions initialized')
}
// Check duplicates
if (window._nc_fileactions.find(search => search.id === action.id)) {
logger.error(`FileAction ${action.id} already registered`, { action })
return
}
window._nc_fileactions.push(action)
}
export const getFileActions = function(): FileAction[] {
return window._nc_fileactions || []
}

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { ContentsWithRoot } from './Navigation'
import type { ContentsWithRoot } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed, DAVResultResponseProps } from 'webdav'
import { File, Folder, davParsePermissions } from '@nextcloud/files'

@ -1,241 +0,0 @@
/**
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* eslint-disable no-use-before-define */
import type { Folder, Node } from '@nextcloud/files'
import isSvg from 'is-svg'
import logger from '../logger.js'
export type ContentsWithRoot = {
folder: Folder,
contents: Node[]
}
export interface Column {
/** Unique column ID */
id: string
/** Translated column title */
title: string
/** The content of the cell. The element will be appended within */
render: (node: Node, view: Navigation) => HTMLElement
/** Function used to sort Nodes between them */
sort?: (nodeA: Node, nodeB: Node) => number
/**
* Custom summary of the column to display at the end of the list.
* Will not be displayed if nothing is provided
*/
summary?: (node: Node[], view: Navigation) => string
}
export interface Navigation {
/** Unique view ID */
id: string
/** Translated view name */
name: string
/** Translated accessible description of the view */
caption?: string
/** Translated title of the empty view */
emptyTitle?: string
/** Translated description of the empty view */
emptyCaption?: string
/**
* Method return the content of the provided path
* This ideally should be a cancellable promise.
* promise.cancel(reason) will be called when the directory
* change and the promise is not resolved yet.
* You _must_ also return the current directory
* information alongside with its content.
*/
getContents: (path: string) => Promise<ContentsWithRoot>
/** The view icon as an inline svg */
icon: string
/** The view order */
order: number
/**
* This view column(s). Name and actions are
* by default always included
*/
columns?: Column[]
/** The empty view element to render your empty content into */
emptyView?: (div: HTMLDivElement) => void
/** The parent unique ID */
parent?: string
/** This view is sticky (sent at the bottom) */
sticky?: boolean
/**
* This view has children and is expanded or not,
* will be overridden by user config.
*/
expanded?: boolean
/**
* Will be used as default if the user
* haven't customized their sorting column
*/
defaultSortKey?: string
}
export class NavigationService {
private _views: Navigation[] = []
private _currentView: Navigation | null = null
constructor() {
logger.debug('Navigation service initialized')
}
register(view: Navigation) {
try {
isValidNavigation(view)
isUniqueNavigation(view, this._views)
} catch (e) {
if (e instanceof Error) {
logger.error(e.message, { view })
}
throw e
}
this._views.push(view)
}
remove(id: string) {
const index = this._views.findIndex(view => view.id === id)
if (index !== -1) {
this._views.splice(index, 1)
}
}
get views(): Navigation[] {
return this._views
}
setActive(view: Navigation | null) {
this._currentView = view
}
get active(): Navigation | null {
return this._currentView
}
}
/**
* Make sure the given view is unique
* and not already registered.
*/
const isUniqueNavigation = function(view: Navigation, views: Navigation[]): boolean {
if (views.find(search => search.id === view.id)) {
throw new Error(`Navigation id ${view.id} is already registered`)
}
return true
}
/**
* Typescript cannot validate an interface.
* Please keep in sync with the Navigation interface requirements.
*/
const isValidNavigation = function(view: Navigation): boolean {
if (!view.id || typeof view.id !== 'string') {
throw new Error('Navigation id is required and must be a string')
}
if (!view.name || typeof view.name !== 'string') {
throw new Error('Navigation name is required and must be a string')
}
if (view.columns && view.columns.length > 0
&& (!view.caption || typeof view.caption !== 'string')) {
throw new Error('Navigation caption is required for top-level views and must be a string')
}
if (!view.getContents || typeof view.getContents !== 'function') {
throw new Error('Navigation getContents is required and must be a function')
}
if (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) {
throw new Error('Navigation icon is required and must be a valid svg string')
}
if (!('order' in view) || typeof view.order !== 'number') {
throw new Error('Navigation order is required and must be a number')
}
// Optional properties
if (view.columns) {
view.columns.forEach(isValidColumn)
}
if (view.emptyView && typeof view.emptyView !== 'function') {
throw new Error('Navigation emptyView must be a function')
}
if (view.parent && typeof view.parent !== 'string') {
throw new Error('Navigation parent must be a string')
}
if ('sticky' in view && typeof view.sticky !== 'boolean') {
throw new Error('Navigation sticky must be a boolean')
}
if ('expanded' in view && typeof view.expanded !== 'boolean') {
throw new Error('Navigation expanded must be a boolean')
}
if (view.defaultSortKey && typeof view.defaultSortKey !== 'string') {
throw new Error('Navigation defaultSortKey must be a string')
}
return true
}
/**
* Typescript cannot validate an interface.
* Please keep in sync with the Column interface requirements.
*/
const isValidColumn = function(column: Column): boolean {
if (!column.id || typeof column.id !== 'string') {
throw new Error('A column id is required')
}
if (!column.title || typeof column.title !== 'string') {
throw new Error('A column title is required')
}
if (!column.render || typeof column.render !== 'function') {
throw new Error('A render function is required')
}
// Optional properties
if (column.sort && typeof column.sort !== 'function') {
throw new Error('Column sortFunction must be a function')
}
if (column.summary && typeof column.summary !== 'function') {
throw new Error('Column summary must be a function')
}
return true
}

@ -19,13 +19,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { ContentsWithRoot } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed, DAVResultResponseProps } from 'webdav'
import { File, Folder, Permission, davParsePermissions } from '@nextcloud/files'
import { generateRemoteUrl } from '@nextcloud/router'
import { getClient, rootPath } from './WebdavClient'
import { getCurrentUser } from '@nextcloud/auth'
import { getClient, rootPath } from './WebdavClient'
import { getDavNameSpaces, getDavProperties } from './DavProperties'
import type { ContentsWithRoot } from './Navigation'
import type { FileStat, ResponseDataDetailed, DAVResultResponseProps } from 'webdav'
const client = getClient(generateRemoteUrl('dav'))

@ -64,11 +64,9 @@
<script lang="ts">
import type { Route } from 'vue-router'
import type { Navigation, ContentsWithRoot } from '../services/Navigation.ts'
import type { UserConfig } from '../types.ts'
import { Folder, Node } from '@nextcloud/files'
import { join } from 'path'
import { Folder, Node, type View, type ContentsWithRoot, join } from 'path'
import { orderBy } from 'natural-orderby'
import { translate } from '@nextcloud/l10n'
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
@ -132,9 +130,9 @@ export default Vue.extend({
return this.userConfigStore.userConfig
},
currentView(): Navigation {
currentView(): View {
return (this.$navigation.active
|| this.$navigation.views.find(view => view.id === 'files')) as Navigation
|| this.$navigation.views.find(view => view.id === 'files')) as View
},
/**

@ -2,13 +2,14 @@ import FolderSvg from '@mdi/svg/svg/folder.svg'
import ShareSvg from '@mdi/svg/svg/share-variant.svg'
import { createTestingPinia } from '@pinia/testing'
import { NavigationService } from '../services/Navigation'
import NavigationView from './Navigation.vue'
import router from '../router/router'
import { useViewConfigStore } from '../store/viewConfig'
import { Folder, View, getNavigation } from '@nextcloud/files'
describe('Navigation renders', () => {
const Navigation = new NavigationService() as NavigationService
delete window._nc_navigation
const Navigation = getNavigation()
before(() => {
cy.mockInitialState('files', 'storageStats', {
@ -38,16 +39,17 @@ describe('Navigation renders', () => {
})
describe('Navigation API', () => {
const Navigation = new NavigationService() as NavigationService
delete window._nc_navigation
const Navigation = getNavigation()
it('Check API entries rendering', () => {
Navigation.register({
Navigation.register(new View({
id: 'files',
name: 'Files',
getContents: () => Promise.resolve(),
getContents: async () => ({ folder: {} as Folder, contents: [] }),
icon: FolderSvg,
order: 1,
})
}))
cy.mount(NavigationView, {
propsData: {
@ -68,13 +70,13 @@ describe('Navigation API', () => {
})
it('Adds a new entry and render', () => {
Navigation.register({
Navigation.register(new View({
id: 'sharing',
name: 'Sharing',
getContents: () => Promise.resolve(),
getContents: async () => ({ folder: {} as Folder, contents: [] }),
icon: ShareSvg,
order: 2,
})
}))
cy.mount(NavigationView, {
propsData: {
@ -95,14 +97,14 @@ describe('Navigation API', () => {
})
it('Adds a new children, render and open menu', () => {
Navigation.register({
Navigation.register(new View({
id: 'sharingin',
name: 'Shared with me',
getContents: () => Promise.resolve(),
getContents: async () => ({ folder: {} as Folder, contents: [] }),
parent: 'sharing',
icon: ShareSvg,
order: 1,
})
}))
cy.mount(NavigationView, {
propsData: {
@ -142,19 +144,20 @@ describe('Navigation API', () => {
it('Throws when adding a duplicate entry', () => {
expect(() => {
Navigation.register({
Navigation.register(new View({
id: 'files',
name: 'Files',
getContents: () => Promise.resolve(),
getContents: async () => ({ folder: {} as Folder, contents: [] }),
icon: FolderSvg,
order: 1,
})
}).to.throw('Navigation id files is already registered')
}))
}).to.throw('View id files is already registered')
})
})
describe('Quota rendering', () => {
const Navigation = new NavigationService()
delete window._nc_navigation
const Navigation = getNavigation()
afterEach(() => cy.unmockInitialState())

@ -83,7 +83,7 @@ import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js
import { setPageHeading } from '../../../../core/src/OCP/accessibility.js'
import { useViewConfigStore } from '../store/viewConfig.ts'
import logger from '../logger.js'
import type { NavigationService, Navigation } from '../services/Navigation.ts'
import type { Navigation, View } from '@nextcloud/files'
import NavigationQuota from '../components/NavigationQuota.vue'
import SettingsModal from './Settings.vue'
@ -125,15 +125,15 @@ export default {
return this.$route?.params?.view || 'files'
},
currentView(): Navigation {
currentView(): View {
return this.views.find(view => view.id === this.currentViewId)
},
views(): Navigation[] {
views(): View[] {
return this.Navigation.views
},
parentViews(): Navigation[] {
parentViews(): View[] {
return this.views
// filter child views
.filter(view => !view.parent)
@ -143,7 +143,7 @@ export default {
})
},
childViews(): Navigation[] {
childViews(): View[] {
return this.views
// filter parent views
.filter(view => !!view.parent)
@ -165,7 +165,7 @@ export default {
this.Navigation.setActive(view)
logger.debug('Navigation changed', { id: view.id, view })
this.showView(view, oldView)
this.showView(view)
}
},
},
@ -178,7 +178,7 @@ export default {
},
methods: {
showView(view: Navigation) {
showView(view: View) {
// Closing any opened sidebar
window?.OCA?.Files?.Sidebar?.close?.()
this.Navigation.setActive(view)
@ -190,7 +190,7 @@ export default {
* Expand/collapse a a view with children and permanently
* save this setting in the server.
*/
onToggleExpand(view: Navigation) {
onToggleExpand(view: View) {
// Invert state
const isExpanded = this.isExpanded(view)
// Update the view expanded state, might not be necessary
@ -202,7 +202,7 @@ export default {
* Check if a view is expanded by user config
* or fallback to the default value.
*/
isExpanded(view: Navigation): boolean {
isExpanded(view: View): boolean {
return typeof this.viewConfigStore.getConfig(view.id)?.expanded === 'boolean'
? this.viewConfigStore.getConfig(view.id).expanded === true
: view.expanded === true
@ -211,7 +211,7 @@ export default {
/**
* Generate the route to a view
*/
generateToNavigation(view: Navigation) {
generateToNavigation(view: View) {
if (view.params) {
const { dir, fileid } = view.params
return { name: 'filelist', params: view.params, query: { dir, fileid } }

@ -19,15 +19,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { expect } from '@jest/globals'
import * as initialState from '@nextcloud/initial-state'
import { Folder } from '@nextcloud/files'
import { basename } from 'path'
import { expect } from '@jest/globals'
import { Folder, Navigation, getNavigation } from '@nextcloud/files'
import * as eventBus from '@nextcloud/event-bus'
import * as initialState from '@nextcloud/initial-state'
import { action } from '../actions/favoriteAction'
import * as favoritesService from '../services/Favorites'
import { NavigationService } from '../services/Navigation'
import registerFavoritesView from './favorites'
jest.mock('webdav/dist/node/request.js', () => ({
@ -38,15 +37,21 @@ global.window.OC = {
TAG_FAVORITE: '_$!<Favorite>!$_',
}
declare global {
interface Window {
_nc_navigation?: Navigation
}
}
describe('Favorites view definition', () => {
let Navigation
beforeEach(() => {
Navigation = new NavigationService()
window.OCP = { Files: { Navigation } }
Navigation = getNavigation()
expect(window._nc_navigation).toBeDefined()
})
afterAll(() => {
delete window.OCP
afterEach(() => {
delete window._nc_navigation
})
test('Default empty favorite view', () => {
@ -114,12 +119,11 @@ describe('Favorites view definition', () => {
describe('Dynamic update of favourite folders', () => {
let Navigation
beforeEach(() => {
Navigation = new NavigationService()
window.OCP = { Files: { Navigation } }
Navigation = getNavigation()
})
afterAll(() => {
delete window.OCP
afterEach(() => {
delete window._nc_navigation
})
test('Add a favorite folder creates a new entry in the navigation', async () => {

@ -19,21 +19,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { Navigation, NavigationService } from '../services/Navigation'
import { basename } from 'path'
import { getLanguage, translate as t } from '@nextcloud/l10n'
import { loadState } from '@nextcloud/initial-state'
import { Node, FileType, View, getNavigation } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
import StarSvg from '@mdi/svg/svg/star.svg?raw'
import { basename } from 'path'
import { getContents } from '../services/Favorites'
import { hashCode } from '../utils/hashUtils'
import { loadState } from '@nextcloud/initial-state'
import { Node, FileType } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
import logger from '../logger'
export const generateFolderView = function(folder: string, index = 0): Navigation {
return {
export const generateFolderView = function(folder: string, index = 0): View {
return new View({
id: generateIdFromPath(folder),
name: basename(folder),
@ -49,7 +48,7 @@ export const generateFolderView = function(folder: string, index = 0): Navigatio
columns: [],
getContents,
} as Navigation
})
}
export const generateIdFromPath = function(path: string): string {
@ -59,10 +58,10 @@ export const generateIdFromPath = function(path: string): string {
export default () => {
// Load state in function for mock testing purposes
const favoriteFolders = loadState<string[]>('files', 'favoriteFolders', [])
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFolderView(folder, index))
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFolderView(folder, index)) as View[]
const Navigation = window.OCP.Files.Navigation as NavigationService
Navigation.register({
const Navigation = getNavigation()
Navigation.register(new View({
id: 'favorites',
name: t('files', 'Favorites'),
caption: t('files', 'List of favorites files and folders.'),
@ -76,7 +75,7 @@ export default () => {
columns: [],
getContents,
} as Navigation)
}))
favoriteFoldersViews.forEach(view => Navigation.register(view))

@ -19,16 +19,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { NavigationService, Navigation } from '../services/Navigation'
import { translate as t } from '@nextcloud/l10n'
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
import { getContents } from '../services/Files'
import { View, getNavigation } from '@nextcloud/files'
export default () => {
const Navigation = window.OCP.Files.Navigation as NavigationService
Navigation.register({
const Navigation = getNavigation()
Navigation.register(new View({
id: 'files',
name: t('files', 'All files'),
caption: t('files', 'List of your files and folders.'),
@ -37,5 +36,5 @@ export default () => {
order: 0,
getContents,
} as Navigation)
}))
}

@ -19,16 +19,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { NavigationService, Navigation } from '../services/Navigation'
import { translate as t } from '@nextcloud/l10n'
import HistorySvg from '@mdi/svg/svg/history.svg?raw'
import { getContents } from '../services/Recent'
import { View, getNavigation } from '@nextcloud/files'
export default () => {
const Navigation = window.OCP.Files.Navigation as NavigationService
Navigation.register({
const Navigation = getNavigation()
Navigation.register(new View({
id: 'recent',
name: t('files', 'Recent'),
caption: t('files', 'List of recently modified files and folders.'),
@ -42,5 +41,5 @@ export default () => {
defaultSortKey: 'mtime',
getContents,
} as Navigation)
}))
}

@ -21,21 +21,19 @@
*/
import { action } from './enterCredentialsAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission } from '@nextcloud/files'
import { DefaultType, FileAction } from '../../../files/src/services/FileAction'
import type { Navigation } from '../../../files/src/services/Navigation'
import { File, Folder, Permission, View, DefaultType, FileAction } from '@nextcloud/files'
import type { StorageConfig } from '../services/externalStorage'
import { STORAGE_STATUS } from '../utils/credentialsUtils'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
const externalStorageView = {
id: 'extstoragemounts',
name: 'External storage',
} as Navigation
} as View
describe('Enter credentials action conditions tests', () => {
test('Default values', () => {

@ -31,7 +31,7 @@ import axios from '@nextcloud/axios'
import LoginSvg from '@mdi/svg/svg/login.svg?raw'
import Vue from 'vue'
import { registerFileAction, FileAction, DefaultType } from '../../../files/src/services/FileAction'
import { registerFileAction, FileAction, DefaultType } from '@nextcloud/files'
import { STORAGE_STATUS, isMissingAuthConfig } from '../utils/credentialsUtils'
import { isNodeExternalStorage } from '../utils/externalStorageUtils'

@ -32,7 +32,7 @@ import '../css/fileEntryStatus.scss'
import { getStatus, type StorageConfig } from '../services/externalStorage'
import { isMissingAuthConfig, STORAGE_STATUS } from '../utils/credentialsUtils'
import { isNodeExternalStorage } from '../utils/externalStorageUtils'
import { registerFileAction, FileAction } from '../../../files/src/services/FileAction'
import { registerFileAction, FileAction } from '@nextcloud/files'
export const action = new FileAction({
id: 'check-external-storage',

@ -21,21 +21,19 @@
*/
import { action } from './openInFilesAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission } from '@nextcloud/files'
import { DefaultType, FileAction } from '../../../files/src/services/FileAction'
import type { Navigation } from '../../../files/src/services/Navigation'
import { Folder, Permission, View, DefaultType, FileAction } from '@nextcloud/files'
import type { StorageConfig } from '../services/externalStorage'
import { STORAGE_STATUS } from '../utils/credentialsUtils'
const view = {
id: 'files',
name: 'Files',
} as Navigation
} as View
const externalStorageView = {
id: 'extstoragemounts',
name: 'External storage',
} as Navigation
} as View
describe('Open in files action conditions tests', () => {
test('Default values', () => {

@ -25,7 +25,7 @@ import type { StorageConfig } from '../services/externalStorage'
import { generateUrl } from '@nextcloud/router'
import { translate as t } from '@nextcloud/l10n'
import { registerFileAction, FileAction, DefaultType } from '../../../files/src/services/FileAction'
import { registerFileAction, FileAction, DefaultType } from '@nextcloud/files'
import { STORAGE_STATUS } from '../utils/credentialsUtils'
export const action = new FileAction({

@ -19,8 +19,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { NavigationService, Navigation } from '../../files/src/services/Navigation'
import { translate as t } from '@nextcloud/l10n'
import { loadState } from '@nextcloud/initial-state'
import FolderNetworkSvg from '@mdi/svg/svg/folder-network.svg?raw'
@ -29,11 +27,12 @@ import './actions/enterCredentialsAction'
import './actions/inlineStorageCheckAction'
import './actions/openInFilesAction'
import { getContents } from './services/externalStorage'
import { View, getNavigation, Column } from '@nextcloud/files'
const allowUserMounting = loadState('files_external', 'allowUserMounting', false)
const Navigation = window.OCP.Files.Navigation as NavigationService
Navigation.register({
const Navigation = getNavigation()
Navigation.register(new View({
id: 'extstoragemounts',
name: t('files_external', 'External storage'),
caption: t('files_external', 'List of external storage.'),
@ -47,7 +46,7 @@ Navigation.register({
order: 30,
columns: [
{
new Column({
id: 'storage-type',
title: t('files_external', 'Storage type'),
render(node) {
@ -56,8 +55,8 @@ Navigation.register({
span.textContent = backend
return span
},
},
{
}),
new Column({
id: 'scope',
title: t('files_external', 'Scope'),
render(node) {
@ -69,8 +68,8 @@ Navigation.register({
span.textContent = scope
return span
},
},
}),
],
getContents,
} as Navigation)
}))

@ -21,8 +21,8 @@
*/
// eslint-disable-next-line n/no-extraneous-import
import type { AxiosResponse } from 'axios'
import type { ContentsWithRoot } from '../../../files/src/services/Navigation'
import type { OCSResponse } from '../../../files_sharing/src/services/SharingService'
import type { ContentsWithRoot } from '@nextcloud/files'
import { Folder, Permission } from '@nextcloud/files'
import { generateOcsUrl, generateRemoteUrl, generateUrl } from '@nextcloud/router'

@ -19,9 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { expect } from '@jest/globals'
import { File, Folder, Permission } from '@nextcloud/files'
import { isNodeExternalStorage } from './externalStorageUtils'
import { expect } from '@jest/globals'
describe('Is node an external storage', () => {
test('A Folder with a backend and a valid scope is an external storage', () => {

@ -0,0 +1,29 @@
OC.L10N.register(
"files_reminders",
{
"File reminders" : "Recordatorios de archivo",
"Reminder for {name}" : "Recordatorio para {name}",
"View file" : "Ver archivo",
"View folder" : "Ver carpeta",
"Set file reminders" : "Establecer recordatorios de archivo",
"**📣 File reminders**\n\nSet file reminders." : "**📣 Recordatorios de archivo**\n\nEstablecer recordatorios de archivo.",
"Back" : "Volver",
"Clear reminder" : "Borrar recordatorio",
"Set custom reminder" : "Configurar recordatorio personalizado",
"Later today" : "Más tarde hoy",
"Set reminder for later today" : "Configurar recordatorio para hoy, más tarde",
"Tomorrow" : "Mañana",
"Set reminder for tomorrow" : "Configurar recordatorio para mañana",
"This weekend" : "Este fin de semana",
"Set reminder for this weekend" : "Configurar recordatorio para este fin de semana",
"Next week" : "Semana siguiente",
"Set reminder for next week" : "Configurar recordatorio para la semana que viene",
"Set reminder at custom date & time" : "Establecer recordatorio a una fecha y hora personalizada",
"Reminder set for \"{fileName}\"" : "Se estableció recordatorio para \"{fileName}\"",
"Failed to set reminder" : "No se pudo configurar el recordatorio",
"Please choose a valid date & time" : "Por favor, escoja una fecha y hora válidas",
"Reminder cleared" : "Se quitó el recordatorio",
"Failed to clear reminder" : "Fallo al quitar el recordatorio",
"Failed to load reminder" : "Fallo al cargar el recordatorio"
},
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");

@ -0,0 +1,27 @@
{ "translations": {
"File reminders" : "Recordatorios de archivo",
"Reminder for {name}" : "Recordatorio para {name}",
"View file" : "Ver archivo",
"View folder" : "Ver carpeta",
"Set file reminders" : "Establecer recordatorios de archivo",
"**📣 File reminders**\n\nSet file reminders." : "**📣 Recordatorios de archivo**\n\nEstablecer recordatorios de archivo.",
"Back" : "Volver",
"Clear reminder" : "Borrar recordatorio",
"Set custom reminder" : "Configurar recordatorio personalizado",
"Later today" : "Más tarde hoy",
"Set reminder for later today" : "Configurar recordatorio para hoy, más tarde",
"Tomorrow" : "Mañana",
"Set reminder for tomorrow" : "Configurar recordatorio para mañana",
"This weekend" : "Este fin de semana",
"Set reminder for this weekend" : "Configurar recordatorio para este fin de semana",
"Next week" : "Semana siguiente",
"Set reminder for next week" : "Configurar recordatorio para la semana que viene",
"Set reminder at custom date & time" : "Establecer recordatorio a una fecha y hora personalizada",
"Reminder set for \"{fileName}\"" : "Se estableció recordatorio para \"{fileName}\"",
"Failed to set reminder" : "No se pudo configurar el recordatorio",
"Please choose a valid date & time" : "Por favor, escoja una fecha y hora válidas",
"Reminder cleared" : "Se quitó el recordatorio",
"Failed to clear reminder" : "Fallo al quitar el recordatorio",
"Failed to load reminder" : "Fallo al cargar el recordatorio"
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
}

@ -210,6 +210,7 @@ OC.L10N.register(
"Expires {relativetime}" : "Läuft {relativetime} ab",
"this share just expired." : "Diese Freigabe ist gerade abgelaufen.",
"Shared with you by {owner}" : "{owner} hat dies mit dir geteilt",
"Open in Files" : "In Dateien öffnen",
"Link to a file" : "Mit einer Datei verknüpfen",
"Error creating the share: {errorMessage}" : "Fehler beim Erstellen der Freigabe: {errorMessage}",
"Error creating the share" : "Fehler beim Erstellen der Freigabe",

@ -208,6 +208,7 @@
"Expires {relativetime}" : "Läuft {relativetime} ab",
"this share just expired." : "Diese Freigabe ist gerade abgelaufen.",
"Shared with you by {owner}" : "{owner} hat dies mit dir geteilt",
"Open in Files" : "In Dateien öffnen",
"Link to a file" : "Mit einer Datei verknüpfen",
"Error creating the share: {errorMessage}" : "Fehler beim Erstellen der Freigabe: {errorMessage}",
"Error creating the share" : "Fehler beim Erstellen der Freigabe",

@ -211,7 +211,8 @@ OC.L10N.register(
"this share just expired." : "ce partage vient d'expirer",
"Shared with you by {owner}" : "Partagé avec vous par {owner}",
"_Accept share_::_Accept shares_" : ["Accepter le partage","Accepter les partages","Accepter les partages"],
"_Reject share_::_Reject shares_" : ["Refuser le partage","Refuser les partages","Refuser les partages"],
"Open in Files" : "Ouvrir dans Fichiers",
"_Reject share_::_Reject shares_" : ["Rejeter le partage","Rejeter les partages","Rejeter les partages"],
"_Restore share_::_Restore shares_" : ["Restaurer le partage","Restaurer les partages","Restaurer les partages"],
"Link to a file" : "Relier à un fichier",
"Error creating the share: {errorMessage}" : "Erreur à la création du partage : {errorMessage} ",
@ -232,7 +233,7 @@ OC.L10N.register(
"Shared with you and the conversation {conversation} by {owner}" : "Partagé avec vous et la conversation {conversation} par {owner}",
"Shared with you in a conversation by {owner}" : "Partagé avec vous dans une conversation de {owner}",
"Shares" : "Partages",
"Overview of shared files." : "Aperçu des fichiers partagés.",
"Overview of shared files." : "Vue d'ensemble des fichiers partagés.",
"No shares" : "Aucun partage",
"Files and folders you shared or have been shared with you will show up here" : "Les fichiers et les dossiers que vous avez partagés ou qui vous ont été partagés apparaîtront ici",
"Shared with you" : "Partagés avec vous",
@ -248,9 +249,9 @@ OC.L10N.register(
"No shared links" : "Aucun partage par lien",
"Files and folders you shared by link will show up here" : "Les fichiers et les dossiers que vous avez partagés par lien apparaîtront ici",
"Deleted shares" : "Partages supprimés",
"List of shares you left." : "Liste des partages auxquels vous avez mis fin.",
"List of shares you left." : "Liste des partages que vous avez quittés.",
"No deleted shares" : "Aucun partage supprimé",
"Shares you have left will show up here" : "Les partages auxquels vous avez mis fin apparaîtront ici",
"Shares you have left will show up here" : "Les partages que vous avez quittés s'afficheront ici",
"Pending shares" : "Partages en attente",
"List of unapproved shares." : "Liste des partages non approuvés.",
"No pending shares" : "Aucun partage en attente",

@ -209,7 +209,8 @@
"this share just expired." : "ce partage vient d'expirer",
"Shared with you by {owner}" : "Partagé avec vous par {owner}",
"_Accept share_::_Accept shares_" : ["Accepter le partage","Accepter les partages","Accepter les partages"],
"_Reject share_::_Reject shares_" : ["Refuser le partage","Refuser les partages","Refuser les partages"],
"Open in Files" : "Ouvrir dans Fichiers",
"_Reject share_::_Reject shares_" : ["Rejeter le partage","Rejeter les partages","Rejeter les partages"],
"_Restore share_::_Restore shares_" : ["Restaurer le partage","Restaurer les partages","Restaurer les partages"],
"Link to a file" : "Relier à un fichier",
"Error creating the share: {errorMessage}" : "Erreur à la création du partage : {errorMessage} ",
@ -230,7 +231,7 @@
"Shared with you and the conversation {conversation} by {owner}" : "Partagé avec vous et la conversation {conversation} par {owner}",
"Shared with you in a conversation by {owner}" : "Partagé avec vous dans une conversation de {owner}",
"Shares" : "Partages",
"Overview of shared files." : "Aperçu des fichiers partagés.",
"Overview of shared files." : "Vue d'ensemble des fichiers partagés.",
"No shares" : "Aucun partage",
"Files and folders you shared or have been shared with you will show up here" : "Les fichiers et les dossiers que vous avez partagés ou qui vous ont été partagés apparaîtront ici",
"Shared with you" : "Partagés avec vous",
@ -246,9 +247,9 @@
"No shared links" : "Aucun partage par lien",
"Files and folders you shared by link will show up here" : "Les fichiers et les dossiers que vous avez partagés par lien apparaîtront ici",
"Deleted shares" : "Partages supprimés",
"List of shares you left." : "Liste des partages auxquels vous avez mis fin.",
"List of shares you left." : "Liste des partages que vous avez quittés.",
"No deleted shares" : "Aucun partage supprimé",
"Shares you have left will show up here" : "Les partages auxquels vous avez mis fin apparaîtront ici",
"Shares you have left will show up here" : "Les partages que vous avez quittés s'afficheront ici",
"Pending shares" : "Partages en attente",
"List of unapproved shares." : "Liste des partages non approuvés.",
"No pending shares" : "Aucun partage en attente",

@ -41,6 +41,7 @@ use OCP\Files\Search\ISearchOperator;
use OCP\Files\StorageNotAvailableException;
use OCP\ICacheFactory;
use OCP\IUserManager;
use OCP\Share\IShare;
/**
* Metadata cache for shared files
@ -55,15 +56,22 @@ class Cache extends CacheJail {
private ?string $ownerDisplayName = null;
private $numericId;
private DisplayNameCache $displayNameCache;
private IShare $share;
/**
* @param SharedStorage $storage
*/
public function __construct($storage, ICacheEntry $sourceRootInfo, DisplayNameCache $displayNameCache) {
public function __construct(
$storage,
ICacheEntry $sourceRootInfo,
DisplayNameCache $displayNameCache,
IShare $share
) {
$this->storage = $storage;
$this->sourceRootInfo = $sourceRootInfo;
$this->numericId = $sourceRootInfo->getStorageId();
$this->displayNameCache = $displayNameCache;
$this->share = $share;
parent::__construct(
null,
@ -150,7 +158,7 @@ class Cache extends CacheJail {
try {
if (isset($entry['permissions'])) {
$entry['permissions'] &= $this->storage->getShare()->getPermissions();
$entry['permissions'] &= $this->share->getPermissions();
} else {
$entry['permissions'] = $this->storage->getPermissions($entry['path']);
}
@ -159,7 +167,7 @@ class Cache extends CacheJail {
// (IDE may say the exception is never thrown false negative)
$sharePermissions = 0;
}
$entry['uid_owner'] = $this->storage->getOwner('');
$entry['uid_owner'] = $this->share->getShareOwner();
$entry['displayname_owner'] = $this->getOwnerDisplayName();
if ($path === '') {
$entry['is_share_mount_point'] = true;
@ -169,7 +177,7 @@ class Cache extends CacheJail {
private function getOwnerDisplayName() {
if (!$this->ownerDisplayName) {
$uid = $this->storage->getOwner('');
$uid = $this->share->getShareOwner();
$this->ownerDisplayName = $this->displayNameCache->getDisplayName($uid) ?? $uid;
}
return $this->ownerDisplayName;

@ -113,8 +113,9 @@ class CleanupRemoteStorages extends Command {
$queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
IQueryBuilder::PARAM_STR)
);
$result = $queryBuilder->execute();
$result = $queryBuilder->executeQuery();
$count = $result->fetchOne();
$result->closeCursor();
$output->writeln("$count files can be deleted for storage $numericId");
}
@ -127,7 +128,7 @@ class CleanupRemoteStorages extends Command {
IQueryBuilder::PARAM_STR)
);
$output->write("deleting $id [$numericId] ... ");
$count = $queryBuilder->execute();
$count = $queryBuilder->executeStatement();
$output->writeln("deleted $count storage");
$this->deleteFiles($numericId, $output);
}
@ -141,7 +142,7 @@ class CleanupRemoteStorages extends Command {
IQueryBuilder::PARAM_STR)
);
$output->write("deleting files for storage $numericId ... ");
$count = $queryBuilder->execute();
$count = $queryBuilder->executeStatement();
$output->writeln("deleted $count files");
}
@ -160,14 +161,16 @@ class CleanupRemoteStorages extends Command {
// but not the ones starting with a '/', they are for normal shares
$queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'),
IQueryBuilder::PARAM_STR)
)->orderBy('numeric_id');
$query = $queryBuilder->execute();
)
->orderBy('numeric_id');
$result = $queryBuilder->executeQuery();
$remoteStorages = [];
while ($row = $query->fetch()) {
while ($row = $result->fetch()) {
$remoteStorages[$row['id']] = $row['numeric_id'];
}
$result->closeCursor();
return $remoteStorages;
}
@ -176,16 +179,17 @@ class CleanupRemoteStorages extends Command {
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->select(['id', 'share_token', 'owner', 'remote'])
->from('share_external');
$query = $queryBuilder->execute();
$result = $queryBuilder->executeQuery();
$remoteShareIds = [];
while ($row = $query->fetch()) {
while ($row = $result->fetch()) {
$cloudId = $this->cloudIdManager->getCloudId($row['owner'], $row['remote']);
$remote = $cloudId->getRemote();
$remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $remote);
}
$result->closeCursor();
return $remoteShareIds;
}

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

Loading…
Cancel
Save