From c91e10d0508eee193188fa8e2b7720ec1241e9b4 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Mon, 5 Aug 2024 18:03:06 +0200 Subject: [PATCH 01/88] Added uploadFile and deleteFile functions to FileService (w.i.p.) --- lib/Controller/AttachmentsController.php | 28 +++++-- lib/Service/FileService.php | 99 +++++++++++++++++++++--- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index 8a2ee33c..d62ba494 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -144,15 +144,25 @@ public function show(string|int $id, ObjectService $objectService): JSONResponse } - /** - * @NoAdminRequired - * @NoCSRFRequired - */ + /** + * @NoAdminRequired + * @NoCSRFRequired + * @throws GuzzleException In case the file upload to NextCloud fails. + */ public function create(ObjectService $objectService, ElasticSearchService $elasticSearchService): JSONResponse { - $data = $this->request->getParams(); + $uploadedFile = $this->request->getUploadedFile('_file'); + // Todo: $uploadedFile['content'] does not contain the file content... + $this->fileService->uploadFile(content: $uploadedFile['content'], filePath: $uploadedFile['name']); + $data['downloadUrl'] = $this->fileService->createShareLink(path: $uploadedFile['name']); + $data['type'] = $uploadedFile['type']; + $data['size'] = $uploadedFile['size']; + $explodedName = explode('.', $uploadedFile['name']); + $data['name'] = $explodedName[0]; + $data['extension'] = end($explodedName); + foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); @@ -188,6 +198,8 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear { $data = $this->request->getParams(); + // Todo: file upload, create new fileService function for updating a file. + foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); @@ -223,9 +235,15 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear /** * @NoAdminRequired * @NoCSRFRequired + * @throws GuzzleException In case deleting the file from NextCloud fails. + * @throws \OCP\DB\Exception In case deleting attachment from the NextCloud DB fails. */ public function destroy(string|int $id, ObjectService $objectService, ElasticSearchService $elasticSearchService): JSONResponse { + $attachment = $this->show($id, $objectService); + // Todo: test this, and are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) + $this->fileService->deleteFile(filePath: $attachment['name']. '.' .$attachment['extension']); + if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' ) { diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index f2f60655..c52ce39f 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -51,11 +51,29 @@ private function getCurrentDomain(): string return $protocol . $host; } + /** + * Gets and returns an array with information about the current user. + * TODO: Username and password used for auth are currently set in config, this should (/could) be dynamic. + * + * @return array An array containing 'username', 'password' for auth and the 'currentUsername'. + */ + private function getUserInfo(): array + { + // Get the current user + $currentUser = $this->userSession->getUser(); + + return [ + '$username' => $this->config->getValueString(app: $this->appName, key: 'adminUsername', default: 'admin'), + '$password' => $this->config->getValueString(app: $this->appName, key: 'adminPassword', default: 'admin'), + '$currentUsername' => $currentUser ? $currentUser->getUID() : 'Guest' + ]; + } + /** * Creates and returns a share link for a file (or folder). * (https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share) * - * @param string $path Path to the file/folder which should be shared. + * @param string $path Path (from root) to the file/folder which should be shared. * @param int|null $shareType 0 = user; 1 = group; 3 = public link; 4 = email; 6 = federated cloud share; 7 = circle; 10 = Talk conversation * @param int|null $permissions 1 = read; 2 = update; 4 = create; 8 = delete; 16 = share; 31 = all (default: 31, for public shares: 1) * @@ -67,17 +85,12 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss // API endpoint to create a share $url = "{$this->getCurrentDomain()}/ocs/v2.php/apps/files_sharing/api/v1/shares"; - // Get the admin username & password for auth - $username = $this->config->getValueString(app: $this->appName, key: 'adminUsername', default: 'admin'); - $password = $this->config->getValueString(app: $this->appName, key: 'adminPassword', default: 'admin'); - - // Get the current username - $currentUser = $this->userSession->getUser(); - $currentUsername = $currentUser ? $currentUser->getUID() : 'Guest'; + // Get the admin username & password for auth & get the current username + $userInfo = $this->getUserInfo(); // Data for the POST request $options = [ - 'auth' => [$username, $password], + 'auth' => [$userInfo['username'], $userInfo['password']], 'headers' => [ 'OCS-APIREQUEST' => 'true', 'Content-Type' => 'application/x-www-form-urlencoded' @@ -86,7 +99,7 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss 'path' => $path, 'shareType' => $shareType, 'permissions' => $permissions, - 'shareWith' => $currentUsername + 'shareWith' => $userInfo['currentUsername'] ] ]; @@ -100,4 +113,70 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss } } + /** + * Uploads a file to nextCloud. + * + * @param mixed $content The content of the file. + * @param string $filePath Path (from root) where to save the file. NOTE: this should include the name and extension/format of the file as well! (example.pdf) + * + * @return bool True if successful. + * @throws GuzzleException|Exception In case the Guzzle call returns an exception. + */ + public function uploadFile(mixed $content, string $filePath = '/'): bool + { + // Get the admin username & password for auth & get the current username + $userInfo = $this->getUserInfo(); + + // API endpoint to upload the file + $url = $this->getCurrentDomain() . '/remote.php/dav/files/' . $userInfo['currentUsername'] . '/' . $filePath; + + try { + $response = $this->client->request('PUT', $url, [ + 'auth' => [$userInfo['username'], $userInfo['password']], + 'body' => $content + ]); + + if ($response->getStatusCode() === 201) { + return true; + } + } catch (\Exception $e) { + $this->logger->error('File upload failed: ' . $e->getMessage()); + throw $e; + } + + return false; + } + + /** + * Deletes a file from nextCloud. + * + * @param string $filePath Path (from root) to the file you want to delete. + * + * @return bool True if successful. + * @throws GuzzleException In case the Guzzle call returns an exception. + */ + public function deleteFile(string $filePath): bool + { + // Get the admin username & password for auth & get the current username + $userInfo = $this->getUserInfo(); + + // API endpoint to upload the file + $url = $this->getCurrentDomain() . '/remote.php/dav/files/' . $userInfo['currentUsername'] . '/' . $filePath; + + try { + $response = $this->client->request('DELETE', $url, [ + 'auth' => [$userInfo['username'], $userInfo['password']], + ]); + + if ($response->getStatusCode() === 204) { + return true; + } + } catch (\Exception $e) { + $this->logger->error('File deletion failed: ' . $e->getMessage()); + throw $e; + } + + return false; + } + } From 0402ab71fb59a635e7d7e4cf94f6c8732743eb0d Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 12:37:15 +0200 Subject: [PATCH 02/88] Some changes to fix create and to get update & delete working for files --- lib/Controller/AttachmentsController.php | 20 +++++++++++------ lib/Service/FileService.php | 28 ++++++++++++++---------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index d62ba494..b7473c10 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -111,8 +111,6 @@ public function index(ObjectService $objectService): JSONResponse } } - - $filters['_schema'] = 'attachment'; $result = $objectService->findObjects(filters: $filters, config: $dbConfig); @@ -160,7 +158,7 @@ public function create(ObjectService $objectService, ElasticSearchService $elast $data['type'] = $uploadedFile['type']; $data['size'] = $uploadedFile['size']; $explodedName = explode('.', $uploadedFile['name']); - $data['name'] = $explodedName[0]; + $data['title'] = $explodedName[0]; $data['extension'] = end($explodedName); foreach($data as $key => $value) { @@ -198,7 +196,15 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear { $data = $this->request->getParams(); - // Todo: file upload, create new fileService function for updating a file. + $uploadedFile = $this->request->getUploadedFile('_file'); + // Todo: $uploadedFile['content'] does not contain the file content... + $this->fileService->uploadFile(content: $uploadedFile['content'], filePath: $uploadedFile['name'], update: true); +// $data['downloadUrl'] = $this->fileService->createShareLink(path: $uploadedFile['name']); + $data['type'] = $uploadedFile['type']; + $data['size'] = $uploadedFile['size']; + $explodedName = explode('.', $uploadedFile['name']); + $data['title'] = $explodedName[0]; + $data['extension'] = end($explodedName); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { @@ -240,9 +246,9 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear */ public function destroy(string|int $id, ObjectService $objectService, ElasticSearchService $elasticSearchService): JSONResponse { - $attachment = $this->show($id, $objectService); - // Todo: test this, and are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) - $this->fileService->deleteFile(filePath: $attachment['name']. '.' .$attachment['extension']); + $attachment = $this->show($id, $objectService)->getData()->jsonSerialize(); + // Todo: are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) + $this->fileService->deleteFile(filePath: $attachment['title']. '.' .$attachment['extension']); if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index c52ce39f..7c716fce 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -63,9 +63,9 @@ private function getUserInfo(): array $currentUser = $this->userSession->getUser(); return [ - '$username' => $this->config->getValueString(app: $this->appName, key: 'adminUsername', default: 'admin'), - '$password' => $this->config->getValueString(app: $this->appName, key: 'adminPassword', default: 'admin'), - '$currentUsername' => $currentUser ? $currentUser->getUID() : 'Guest' + 'username' => $this->config->getValueString(app: $this->appName, key: 'adminUsername', default: 'admin'), + 'password' => $this->config->getValueString(app: $this->appName, key: 'adminPassword', default: 'admin'), + 'currentUsername' => $currentUser ? $currentUser->getUID() : 'Guest' ]; } @@ -114,21 +114,23 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss } /** - * Uploads a file to nextCloud. + * Uploads a file to nextCloud. Will overwrite a file if it already exists and create a new one if it doesn't exist. * * @param mixed $content The content of the file. - * @param string $filePath Path (from root) where to save the file. NOTE: this should include the name and extension/format of the file as well! (example.pdf) + * @param string|null $filePath Path (from root) where to save the file. NOTE: this should include the name and extension/format of the file as well! (example.pdf) + * @param bool|null $update If set to true, the response status code 204 will also be seen as a success result. (NextCloud will return 204 when successfully updating a file) * * @return bool True if successful. - * @throws GuzzleException|Exception In case the Guzzle call returns an exception. + * @throws GuzzleException In case the Guzzle call returns an exception. */ - public function uploadFile(mixed $content, string $filePath = '/'): bool + public function uploadFile(mixed $content, ?string $filePath = '', ?bool $update = false): bool { // Get the admin username & password for auth & get the current username $userInfo = $this->getUserInfo(); // API endpoint to upload the file - $url = $this->getCurrentDomain() . '/remote.php/dav/files/' . $userInfo['currentUsername'] . '/' . $filePath; + $url = $this->getCurrentDomain() . '/remote.php/dav/files/' + . $userInfo['currentUsername'] . '/' . ltrim(string: $filePath, characters: '/'); try { $response = $this->client->request('PUT', $url, [ @@ -136,11 +138,12 @@ public function uploadFile(mixed $content, string $filePath = '/'): bool 'body' => $content ]); - if ($response->getStatusCode() === 201) { + if ($response->getStatusCode() === 201 || ($update === true && $response->getStatusCode() === 204)) { return true; } } catch (\Exception $e) { - $this->logger->error('File upload failed: ' . $e->getMessage()); + $str = $update === true ? 'update' : 'upload'; + $this->logger->error("File $str failed: " . $e->getMessage()); throw $e; } @@ -153,7 +156,7 @@ public function uploadFile(mixed $content, string $filePath = '/'): bool * @param string $filePath Path (from root) to the file you want to delete. * * @return bool True if successful. - * @throws GuzzleException In case the Guzzle call returns an exception. + * @throws GuzzleException|Exception In case the Guzzle call returns an exception. */ public function deleteFile(string $filePath): bool { @@ -161,7 +164,8 @@ public function deleteFile(string $filePath): bool $userInfo = $this->getUserInfo(); // API endpoint to upload the file - $url = $this->getCurrentDomain() . '/remote.php/dav/files/' . $userInfo['currentUsername'] . '/' . $filePath; + $url = $this->getCurrentDomain() . '/remote.php/dav/files/' + . $userInfo['currentUsername'] . '/' . ltrim(string: $filePath, characters: '/'); try { $response = $this->client->request('DELETE', $url, [ From 69842b635ec62f7c606bcf6fc89331b81ce8dd5f Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 14:43:03 +0200 Subject: [PATCH 03/88] Let's not allow posting/putting of the id field --- lib/Controller/AttachmentsController.php | 7 ++++--- lib/Controller/CatalogiController.php | 6 ++++-- lib/Controller/DirectoryController.php | 12 ++++++------ lib/Controller/MetaDataController.php | 9 +++++---- lib/Controller/PublicationsController.php | 11 ++++++----- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index b7473c10..ba54fc2b 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -161,6 +161,8 @@ public function create(ObjectService $objectService, ElasticSearchService $elast $data['title'] = $explodedName[0]; $data['extension'] = end($explodedName); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); @@ -206,14 +208,13 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear $data['title'] = $explodedName[0]; $data['extension'] = end($explodedName); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); } } - if (isset($data['id'])) { - unset( $data['id']); - } if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' diff --git a/lib/Controller/CatalogiController.php b/lib/Controller/CatalogiController.php index 299c23e7..4da5e22b 100644 --- a/lib/Controller/CatalogiController.php +++ b/lib/Controller/CatalogiController.php @@ -63,7 +63,7 @@ public function index(ObjectService $objectService): JSONResponse if (str_starts_with($key, '_')) { unset($filters[$key]); - } + } } if($this->config->hasKey($this->appName, 'mongoStorage') === false @@ -74,7 +74,7 @@ public function index(ObjectService $objectService): JSONResponse return new JSONResponse(['results' => $this->catalogMapper->findAll(filters: $filters, searchParams: $searchParams, searchConditions: $searchConditions)]); } - + try { $dbConfig = [ 'base_uri' => $this->config->getValueString($this->appName, 'mongodbLocation'), @@ -129,6 +129,8 @@ public function create(ObjectService $objectService): JSONResponse { $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach ($data as $key => $value) { if (str_starts_with($key, '_')) { unset($data[$key]); diff --git a/lib/Controller/DirectoryController.php b/lib/Controller/DirectoryController.php index 2e60a53a..cfcf7110 100644 --- a/lib/Controller/DirectoryController.php +++ b/lib/Controller/DirectoryController.php @@ -110,7 +110,7 @@ public function index(ObjectService $objectService): JSONResponse if (str_starts_with($key, '_')) { unset($filters[$key]); - } + } } if($this->config->hasKey($this->appName, 'mongoStorage') === false @@ -121,7 +121,7 @@ public function index(ObjectService $objectService): JSONResponse return new JSONResponse(['results' => $this->listingMapper->findAll(filters: $filters, searchParams: $searchParams, searchConditions: $searchConditions)]); } - + $dbConfig['base_uri'] = $this->config->getValueString(app: $this->appName, key: 'mongodbLocation'); $dbConfig['headers']['api-key'] = $this->config->getValueString(app: $this->appName, key: 'mongodbKey'); $dbConfig['mongodbCluster'] = $this->config->getValueString(app: $this->appName, key: 'mongodbCluster'); @@ -163,9 +163,10 @@ public function show(string|int $id, ObjectService $objectService, DirectoryServ */ public function create(ObjectService $objectService, DirectoryService $directoryService): JSONResponse { - $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); @@ -204,14 +205,13 @@ public function update(string|int $id, ObjectService $objectService): JSONRespon $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); } } - if (isset($data['id'])) { - unset( $data['id']); - } if($this->config->hasKey($this->appName, 'mongoStorage') === false diff --git a/lib/Controller/MetaDataController.php b/lib/Controller/MetaDataController.php index 0001149b..6874da4b 100644 --- a/lib/Controller/MetaDataController.php +++ b/lib/Controller/MetaDataController.php @@ -72,7 +72,7 @@ public function index(ObjectService $objectService): JSONResponse if (str_starts_with($key, '_')) { unset($filters[$key]); - } + } } if($this->config->hasKey($this->appName, 'mongoStorage') === false @@ -128,6 +128,8 @@ public function create(ObjectService $objectService): JSONResponse $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); @@ -162,14 +164,13 @@ public function update(string|int $id, ObjectService $objectService): JSONRespon { $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); } } - if (isset($data['id'])) { - unset( $data['id']); - } if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' diff --git a/lib/Controller/PublicationsController.php b/lib/Controller/PublicationsController.php index 9095c371..ebdcf1e4 100644 --- a/lib/Controller/PublicationsController.php +++ b/lib/Controller/PublicationsController.php @@ -138,7 +138,7 @@ public function index(ObjectService $objectService): JSONResponse if (str_starts_with($key, '_')) { unset($filters[$key]); - } + } } if($this->config->hasKey($this->appName, 'mongoStorage') === false @@ -153,7 +153,7 @@ public function index(ObjectService $objectService): JSONResponse $dbConfig['base_uri'] = $this->config->getValueString(app: $this->appName, key: 'mongodbLocation'); $dbConfig['headers']['api-key'] = $this->config->getValueString(app: $this->appName, key: 'mongodbKey'); $dbConfig['mongodbCluster'] = $this->config->getValueString(app: $this->appName, key: 'mongodbCluster'); - + $filters['_schema'] = 'publication'; $result = $objectService->findObjects(filters: $filters, config: $dbConfig); @@ -194,6 +194,8 @@ public function create(ObjectService $objectService, ElasticSearchService $elast { $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); @@ -248,14 +250,13 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear $data = $this->request->getParams(); + // Remove fields we should never post + unset($data['id']); foreach($data as $key => $value) { if(str_starts_with($key, '_')) { unset($data[$key]); } } - if (isset($data['id'])) { - unset( $data['id']); - } if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' From 1e60d0075da2c027600d9f42ea5340c37ac670b5 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 15:19:41 +0200 Subject: [PATCH 04/88] Create Folder for attachments --- lib/Controller/AttachmentsController.php | 17 ++++++--- lib/Service/FileService.php | 48 +++++++++++++++++++----- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index ba54fc2b..64e0409d 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -152,9 +152,10 @@ public function create(ObjectService $objectService, ElasticSearchService $elast $data = $this->request->getParams(); $uploadedFile = $this->request->getUploadedFile('_file'); + $this->fileService->createFolder('Attachments'); // Todo: $uploadedFile['content'] does not contain the file content... - $this->fileService->uploadFile(content: $uploadedFile['content'], filePath: $uploadedFile['name']); - $data['downloadUrl'] = $this->fileService->createShareLink(path: $uploadedFile['name']); + $this->fileService->uploadFile(content: $uploadedFile['content'], filePath: 'Attachments/'.$uploadedFile['name']); + $data['downloadUrl'] = $this->fileService->createShareLink(path: 'Attachments/'.$uploadedFile['name']); $data['type'] = $uploadedFile['type']; $data['size'] = $uploadedFile['size']; $explodedName = explode('.', $uploadedFile['name']); @@ -193,15 +194,21 @@ public function create(ObjectService $objectService, ElasticSearchService $elast /** * @NoAdminRequired * @NoCSRFRequired + * @throws GuzzleException In case updating the file in NextCloud fails. */ public function update(string|int $id, ObjectService $objectService, ElasticSearchService $elasticSearchService): JSONResponse { $data = $this->request->getParams(); $uploadedFile = $this->request->getUploadedFile('_file'); + $this->fileService->createFolder('Attachments'); // Todo: $uploadedFile['content'] does not contain the file content... - $this->fileService->uploadFile(content: $uploadedFile['content'], filePath: $uploadedFile['name'], update: true); -// $data['downloadUrl'] = $this->fileService->createShareLink(path: $uploadedFile['name']); + $this->fileService->uploadFile( + content: $uploadedFile['content'], + filePath: 'Attachments/'.$uploadedFile['name'], + update: true + ); +// $data['downloadUrl'] = $this->fileService->createShareLink(path: 'Attachments/'.$uploadedFile['name']); $data['type'] = $uploadedFile['type']; $data['size'] = $uploadedFile['size']; $explodedName = explode('.', $uploadedFile['name']); @@ -249,7 +256,7 @@ public function destroy(string|int $id, ObjectService $objectService, ElasticSea { $attachment = $this->show($id, $objectService)->getData()->jsonSerialize(); // Todo: are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) - $this->fileService->deleteFile(filePath: $attachment['title']. '.' .$attachment['extension']); + $this->fileService->deleteFile(filePath: 'Attachments/' . $attachment['title'] . '.' . $attachment['extension']); if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index 7c716fce..feeff1b1 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -114,14 +114,14 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss } /** - * Uploads a file to nextCloud. Will overwrite a file if it already exists and create a new one if it doesn't exist. + * Uploads a file to NextCloud. Will overwrite a file if it already exists and create a new one if it doesn't exist. * * @param mixed $content The content of the file. * @param string|null $filePath Path (from root) where to save the file. NOTE: this should include the name and extension/format of the file as well! (example.pdf) * @param bool|null $update If set to true, the response status code 204 will also be seen as a success result. (NextCloud will return 204 when successfully updating a file) * * @return bool True if successful. - * @throws GuzzleException In case the Guzzle call returns an exception. + * @throws GuzzleException|Exception In case the Guzzle call returns an exception. */ public function uploadFile(mixed $content, ?string $filePath = '', ?bool $update = false): bool { @@ -130,7 +130,7 @@ public function uploadFile(mixed $content, ?string $filePath = '', ?bool $update // API endpoint to upload the file $url = $this->getCurrentDomain() . '/remote.php/dav/files/' - . $userInfo['currentUsername'] . '/' . ltrim(string: $filePath, characters: '/'); + . $userInfo['currentUsername'] . '/' . trim(string: $filePath, characters: '/'); try { $response = $this->client->request('PUT', $url, [ @@ -138,10 +138,11 @@ public function uploadFile(mixed $content, ?string $filePath = '', ?bool $update 'body' => $content ]); + // 201 Created indicates that the file was created, 204 No Content indicates that the file was updated. if ($response->getStatusCode() === 201 || ($update === true && $response->getStatusCode() === 204)) { return true; } - } catch (\Exception $e) { + } catch (Exception $e) { $str = $update === true ? 'update' : 'upload'; $this->logger->error("File $str failed: " . $e->getMessage()); throw $e; @@ -151,7 +152,7 @@ public function uploadFile(mixed $content, ?string $filePath = '', ?bool $update } /** - * Deletes a file from nextCloud. + * Deletes a file from NextCloud. * * @param string $filePath Path (from root) to the file you want to delete. * @@ -163,19 +164,19 @@ public function deleteFile(string $filePath): bool // Get the admin username & password for auth & get the current username $userInfo = $this->getUserInfo(); - // API endpoint to upload the file + // API endpoint to delete the file $url = $this->getCurrentDomain() . '/remote.php/dav/files/' - . $userInfo['currentUsername'] . '/' . ltrim(string: $filePath, characters: '/'); + . $userInfo['currentUsername'] . '/' . trim(string: $filePath, characters: '/'); try { $response = $this->client->request('DELETE', $url, [ 'auth' => [$userInfo['username'], $userInfo['password']], ]); - if ($response->getStatusCode() === 204) { + if ($response->getStatusCode() === 204) { // 204 No Content indicates the file was deleted. return true; } - } catch (\Exception $e) { + } catch (Exception $e) { $this->logger->error('File deletion failed: ' . $e->getMessage()); throw $e; } @@ -183,4 +184,33 @@ public function deleteFile(string $filePath): bool return false; } + /** + * Creates a new folder in NextCloud, unless it already exists. + * + * @param string $folderPath Path (from root) to where you want to create a folder. NOTE: this should include the name of the folder as well! (/Media/exampleFolder) + * + * @return bool True if successfully created a new folder. + * @throws GuzzleException|Exception In case the Guzzle call returns an exception. + */ + public function createFolder(string $folderPath): bool + { + // Get the admin username & password for auth & get the current username + $userInfo = $this->getUserInfo(); + + // API endpoint to create a folder + $url = $this->getCurrentDomain() . '/remote.php/dav/files/' + . $userInfo['currentUsername'] . '/' . trim(string: $folderPath, characters: '/'); + + try { + $response = $this->client->request('MKCOL', $url, [ + 'auth' => [$userInfo['username'], $userInfo['password']], + ]); + + return $response->getStatusCode() === 201; // 201 Created indicates the folder was created. + } catch (\Exception $e) { + $this->logger->error('Folder creation failed: ' . $e->getMessage()); + throw $e; + } + } + } From 561a1d338118d6fe807d6d2e32af5811313232d7 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 15:41:49 +0200 Subject: [PATCH 05/88] Let's first check if a folder already exists before creating it --- lib/Service/FileService.php | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index feeff1b1..c9fd280f 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -184,6 +184,43 @@ public function deleteFile(string $filePath): bool return false; } + /** + * Checks if a folder exists in NextCloud. + * + * @param string $folderPath Path (from root) to a folder you want to check if exists. + * + * @return bool True if the folder exists. + * @throws GuzzleException|Exception In case the Guzzle call returns an exception. + */ + public function folderExists(string $folderPath): bool + { + // Get the admin username & password for auth & get the current username + $userInfo = $this->getUserInfo(); + + // API endpoint to check if a folder exists + $url = $this->getCurrentDomain() . '/remote.php/dav/files/' + . $userInfo['currentUsername'] . '/' . trim(string: $folderPath, characters: '/'); + + try { + $response = $this->client->request('PROPFIND', $url, [ + 'auth' => [$userInfo['username'], $userInfo['password']], + 'headers' => [ + 'Depth' => '1', + ], + 'body' => '', + ]); + + return $response->getStatusCode() === 207; // Multi-Status indicates the folder exists. + } catch (Exception $e) { + if ($e->getCode() === 404) { + return false; + } + + $this->logger->error('Folder existence check failed: ' . $e->getMessage()); + throw $e; + } + } + /** * Creates a new folder in NextCloud, unless it already exists. * @@ -194,6 +231,11 @@ public function deleteFile(string $filePath): bool */ public function createFolder(string $folderPath): bool { + if ($this->folderExists($folderPath) === true) { + $this->logger->info('Folder creation failed: Folder already exists'); + return false; + } + // Get the admin username & password for auth & get the current username $userInfo = $this->getUserInfo(); From 91f66a94a4779bde8a4d31ed7e23f3ccd8e235e7 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 16:58:24 +0200 Subject: [PATCH 06/88] small fix on publication --- lib/Db/Publication.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Db/Publication.php b/lib/Db/Publication.php index c7d762e6..5c5defaa 100644 --- a/lib/Db/Publication.php +++ b/lib/Db/Publication.php @@ -78,7 +78,7 @@ public function hydrate(array $object): self foreach($object as $key => $value) { if (in_array($key, $jsonFields) === true && $value === []) { - $value = []; + $value = null; } $method = 'set'.ucfirst($key); @@ -90,6 +90,7 @@ public function hydrate(array $object): self } } + // Todo: MetaData is depricated, we should use Schema instead. But this needs front-end changes as well. $this->setSchema($this->getMetaData()); $this->setAttachmentCount('0'); From c4c44d0e1ba3ef4a47cf85705489200d0cf9110d Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 17:30:12 +0200 Subject: [PATCH 07/88] Fix getting file content from attachment body and saving it in nextcloud --- lib/Controller/AttachmentsController.php | 81 ++++++++++++++++-------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/lib/Controller/AttachmentsController.php b/lib/Controller/AttachmentsController.php index 64e0409d..6ed8af92 100644 --- a/lib/Controller/AttachmentsController.php +++ b/lib/Controller/AttachmentsController.php @@ -94,8 +94,8 @@ public function catalog(string|int $id): TemplateResponse */ public function index(ObjectService $objectService): JSONResponse { - if($this->config->hasKey($this->appName, 'mongoStorage') === false - || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' + if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false + || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' ) { return new JSONResponse(['results' =>$this->attachmentMapper->findAll()]); } @@ -125,8 +125,8 @@ public function index(ObjectService $objectService): JSONResponse */ public function show(string|int $id, ObjectService $objectService): JSONResponse { - if($this->config->hasKey($this->appName, 'mongoStorage') === false - || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' + if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false + || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' ) { return new JSONResponse($this->attachmentMapper->find(id: (int) $id)); } @@ -151,27 +151,42 @@ public function create(ObjectService $objectService, ElasticSearchService $elast { $data = $this->request->getParams(); - $uploadedFile = $this->request->getUploadedFile('_file'); - $this->fileService->createFolder('Attachments'); - // Todo: $uploadedFile['content'] does not contain the file content... - $this->fileService->uploadFile(content: $uploadedFile['content'], filePath: 'Attachments/'.$uploadedFile['name']); + // Check if a file was uploaded + $uploadedFile = $this->request->getUploadedFile(key: '_file'); + if (empty($uploadedFile) === true) { + return new JSONResponse(data: ['error' => 'No file uploaded for key "_file"'], statusCode: 400); + } + + // Check for upload errors + if ($uploadedFile['error'] !== UPLOAD_ERR_OK) { + return new JSONResponse(data: ['error' => 'File upload error: '.$uploadedFile['error']], statusCode: 400); + } + + // Save the uploaded file + $this->fileService->createFolder(folderPath: 'Attachments'); + $this->fileService->uploadFile( + content: file_get_contents(filename: $uploadedFile['tmp_name']), + filePath: 'Attachments/'.$uploadedFile['name'] + ); + + // Update Attachment data $data['downloadUrl'] = $this->fileService->createShareLink(path: 'Attachments/'.$uploadedFile['name']); $data['type'] = $uploadedFile['type']; $data['size'] = $uploadedFile['size']; - $explodedName = explode('.', $uploadedFile['name']); + $explodedName = explode(separator: '.', string: $uploadedFile['name']); $data['title'] = $explodedName[0]; - $data['extension'] = end($explodedName); + $data['extension'] = end(array: $explodedName); // Remove fields we should never post unset($data['id']); foreach($data as $key => $value) { - if(str_starts_with($key, '_')) { + if(str_starts_with(haystack: $key, needle: '_')) { unset($data[$key]); } } - if($this->config->hasKey($this->appName, 'mongoStorage') === false - || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' + if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false + || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' ) { return new JSONResponse($this->attachmentMapper->createFromArray(object: $data)); } @@ -200,31 +215,36 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear { $data = $this->request->getParams(); - $uploadedFile = $this->request->getUploadedFile('_file'); - $this->fileService->createFolder('Attachments'); - // Todo: $uploadedFile['content'] does not contain the file content... + // Todo: $uploadedFile is empty when doing a PUT... + $uploadedFile = $this->request->getUploadedFile(key: '_file'); + + // Save the uploaded file + $this->fileService->createFolder(folderPath: 'Attachments'); $this->fileService->uploadFile( - content: $uploadedFile['content'], + content: file_get_contents(filename: $uploadedFile['tmp_name']), filePath: 'Attachments/'.$uploadedFile['name'], update: true ); + + // Update Attachment data + // Todo: when should we create a new share link? // $data['downloadUrl'] = $this->fileService->createShareLink(path: 'Attachments/'.$uploadedFile['name']); $data['type'] = $uploadedFile['type']; $data['size'] = $uploadedFile['size']; - $explodedName = explode('.', $uploadedFile['name']); + $explodedName = explode(separator: '.', string: $uploadedFile['name']); $data['title'] = $explodedName[0]; - $data['extension'] = end($explodedName); + $data['extension'] = end(array: $explodedName); // Remove fields we should never post unset($data['id']); foreach($data as $key => $value) { - if(str_starts_with($key, '_')) { + if(str_starts_with(haystack: $key, needle: '_')) { unset($data[$key]); } } - if($this->config->hasKey($this->appName, 'mongoStorage') === false - || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' + if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false + || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' ) { return new JSONResponse($this->attachmentMapper->updateFromArray(id: (int) $id, object: $data)); } @@ -254,14 +274,16 @@ public function update(string|int $id, ObjectService $objectService, ElasticSear */ public function destroy(string|int $id, ObjectService $objectService, ElasticSearchService $elasticSearchService): JSONResponse { - $attachment = $this->show($id, $objectService)->getData()->jsonSerialize(); - // Todo: are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) - $this->fileService->deleteFile(filePath: 'Attachments/' . $attachment['title'] . '.' . $attachment['extension']); + $attachment = $this->show(id: $id, objectService: $objectService)->getData(); - if($this->config->hasKey($this->appName, 'mongoStorage') === false - || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' + if ($this->config->hasKey(app: $this->appName, key: 'mongoStorage') === false + || $this->config->getValueString(app: $this->appName, key: 'mongoStorage') !== '1' ) { - $this->attachmentMapper->delete($this->attachmentMapper->find((int) $id)); + $attachment = $attachment->jsonSerialize(); + + // Todo: are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) + $this->fileService->deleteFile(filePath: 'Attachments/' . $attachment['title'] . '.' . $attachment['extension']); + $this->attachmentMapper->delete(entity: $this->attachmentMapper->find(id: (int) $id)); return new JSONResponse([]); } @@ -270,6 +292,9 @@ public function destroy(string|int $id, ObjectService $objectService, ElasticSea $dbConfig['headers']['api-key'] = $this->config->getValueString(app: $this->appName, key: 'mongodbKey'); $dbConfig['mongodbCluster'] = $this->config->getValueString(app: $this->appName, key: 'mongodbCluster'); + // Todo: are we sure this is the best way to do this (how do we save the full path to this file in nextCloud) + $this->fileService->deleteFile(filePath: 'Attachments/' . $attachment['title'] . '.' . $attachment['extension']); + $filters['_id'] = (string) $id; $returnData = $objectService->deleteObject( filters: $filters, From 198539d2966b3921625aedf942138edfd0d65a36 Mon Sep 17 00:00:00 2001 From: Wilco Louwerse Date: Tue, 6 Aug 2024 17:58:34 +0200 Subject: [PATCH 08/88] Small code cleanup --- lib/Service/FileService.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index c9fd280f..2d70bc85 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -133,7 +133,7 @@ public function uploadFile(mixed $content, ?string $filePath = '', ?bool $update . $userInfo['currentUsername'] . '/' . trim(string: $filePath, characters: '/'); try { - $response = $this->client->request('PUT', $url, [ + $response = $this->client->request(method: 'PUT', uri: $url, options: [ 'auth' => [$userInfo['username'], $userInfo['password']], 'body' => $content ]); @@ -169,7 +169,7 @@ public function deleteFile(string $filePath): bool . $userInfo['currentUsername'] . '/' . trim(string: $filePath, characters: '/'); try { - $response = $this->client->request('DELETE', $url, [ + $response = $this->client->request(method: 'DELETE', uri: $url, options: [ 'auth' => [$userInfo['username'], $userInfo['password']], ]); @@ -202,7 +202,7 @@ public function folderExists(string $folderPath): bool . $userInfo['currentUsername'] . '/' . trim(string: $folderPath, characters: '/'); try { - $response = $this->client->request('PROPFIND', $url, [ + $response = $this->client->request(method: 'PROPFIND', uri: $url, options: [ 'auth' => [$userInfo['username'], $userInfo['password']], 'headers' => [ 'Depth' => '1', @@ -231,7 +231,7 @@ public function folderExists(string $folderPath): bool */ public function createFolder(string $folderPath): bool { - if ($this->folderExists($folderPath) === true) { + if ($this->folderExists(folderPath: $folderPath) === true) { $this->logger->info('Folder creation failed: Folder already exists'); return false; } @@ -244,7 +244,7 @@ public function createFolder(string $folderPath): bool . $userInfo['currentUsername'] . '/' . trim(string: $folderPath, characters: '/'); try { - $response = $this->client->request('MKCOL', $url, [ + $response = $this->client->request(method: 'MKCOL', uri: $url, options: [ 'auth' => [$userInfo['username'], $userInfo['password']], ]); From 918538159838144a87eaf80cb7338aa5f9031807 Mon Sep 17 00:00:00 2001 From: Remko Date: Tue, 6 Aug 2024 20:41:03 +0200 Subject: [PATCH 09/88] WIP --- .vscode/settings.json | 18 +- css/main.css | 32 +++ package-lock.json | 240 ++++--------------- package.json | 2 + src/modals/attachment/AddAttachmentModal.vue | 46 +++- src/views/publications/PublicationDetail.vue | 45 ++-- 6 files changed, 175 insertions(+), 208 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e6b78e0b..596304cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,18 @@ { "files.autoSave": "afterDelay", -"editor.defaultFormatter": "dbaeumer.vscode-eslint", -"editor.formatOnSave": true, -"cSpell.words": [ + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": false, + }, + "editor.formatOnSave": true, + "eslint.format.enable": true, + + "[javascript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + + "cSpell.words": [ "gedepubliceerd", "depubliceren", "Depubliceren", @@ -11,5 +21,5 @@ "opencatalogi", "pinia", "Toegangs" -] + ], } diff --git a/css/main.css b/css/main.css index 36dd36e1..9d333b48 100644 --- a/css/main.css +++ b/css/main.css @@ -112,3 +112,35 @@ .errorMessage { color: var(--color-error); } + + +/* File drag and drop */ + +.filesListDragDropNotice{ + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 113px; + margin: 0; + user-select: none; + color: var(--color-text-maxcontrast); + background-color: var(--color-main-background); + border-color: #000; +} + +.filesListDragDropNoticeWrapper{ + display: flex; + align-items: center; + justify-content: center; + height: 15vh; + max-height: 70%; + padding: 0 5vw; + border: 2px var(--color-border-dark) dashed; + border-radius: var(--border-radius-large); +} + +.filesListDragDropNoticeTitle{ + margin-left: 16px; + color: inherit; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 67e081ec..2db57607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@nextcloud/l10n": "^2.0.1", "@nextcloud/router": "^2.0.1", "@nextcloud/vue": "^8.12.0", + "@vueuse/core": "^10.11.0", "apexcharts": "^3.50.0", "bootstrap-vue": "^2.23.1", "css-loader": "^6.8.1", @@ -42,6 +43,7 @@ "@types/node": "^20.14.12", "@vue/test-utils": "^2.4.6", "@vue/vue2-jest": "^29.0.0", + "eslint": "^8.57.0", "eslint-webpack-plugin": "^4.2.0", "jest": "^29.0.0", "jest-environment-jsdom": "^29.7.0", @@ -1939,7 +1941,6 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "peer": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -1955,7 +1956,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1968,7 +1968,6 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, - "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -1978,7 +1977,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2001,15 +1999,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2020,7 +2016,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2036,7 +2031,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -2049,7 +2043,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2062,7 +2055,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -2075,7 +2067,6 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2138,7 +2129,6 @@ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "deprecated": "Use @eslint/config-array instead", "dev": true, - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -2153,7 +2143,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2164,7 +2153,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2177,7 +2165,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "peer": true, "engines": { "node": ">=12.22" }, @@ -2191,8 +2178,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "peer": true + "dev": true }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -4640,6 +4626,7 @@ "version": "10.11.0", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz", "integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==", + "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.0", @@ -5007,7 +4994,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5040,7 +5026,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7580,8 +7565,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -7785,7 +7769,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8290,7 +8273,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8869,7 +8851,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8884,15 +8865,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8903,7 +8882,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8920,7 +8898,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -8932,15 +8909,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -8953,7 +8928,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -8970,7 +8944,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -8983,7 +8956,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -8993,7 +8965,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -9010,7 +8981,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -9026,7 +8996,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -9036,7 +9005,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -9049,7 +9017,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -9065,7 +9032,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9078,7 +9044,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -9094,7 +9059,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9107,7 +9071,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -9120,7 +9083,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "peer": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -9138,7 +9100,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9164,7 +9125,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -9177,7 +9137,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -9186,7 +9145,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -9198,7 +9156,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "peer": true, "engines": { "node": ">=4.0" } @@ -9452,8 +9409,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fast-uri": { "version": "3.0.1", @@ -9462,9 +9418,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "funding": [ { @@ -9476,6 +9432,7 @@ "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "peer": true, "dependencies": { "strnum": "^1.0.5" @@ -9530,7 +9487,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -9692,7 +9648,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -9706,8 +9661,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/floating-vue": { "version": "1.0.0-beta.19", @@ -10013,7 +9967,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -10174,8 +10127,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "peer": true + "dev": true }, "node_modules/handle-thing": { "version": "2.0.1", @@ -10735,7 +10687,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -10752,7 +10703,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -11198,7 +11148,6 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -13413,8 +13362,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -13424,15 +13372,13 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "peer": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json5": { "version": "2.2.3", @@ -13451,7 +13397,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -13507,7 +13452,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -13605,8 +13549,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lodash.throttle": { "version": "4.1.1", @@ -15247,7 +15190,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -15350,7 +15292,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -15849,7 +15790,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -16755,7 +16695,6 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -18492,8 +18431,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/thunky": { "version": "1.1.0", @@ -18895,7 +18833,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -19245,7 +19182,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -20067,7 +20003,6 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -21593,7 +21528,6 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "peer": true, "requires": { "eslint-visitor-keys": "^3.3.0" }, @@ -21602,8 +21536,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "peer": true + "dev": true } } }, @@ -21611,15 +21544,13 @@ "version": "4.11.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", - "dev": true, - "peer": true + "dev": true }, "@eslint/eslintrc": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "peer": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -21636,15 +21567,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -21655,7 +21584,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "peer": true, "requires": { "type-fest": "^0.20.2" } @@ -21665,7 +21593,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "peer": true, "requires": { "argparse": "^2.0.1" } @@ -21675,7 +21602,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "requires": { "brace-expansion": "^1.1.7" } @@ -21684,8 +21610,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -21693,8 +21618,7 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "peer": true + "dev": true }, "@floating-ui/core": { "version": "1.6.5", @@ -21744,7 +21668,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, - "peer": true, "requires": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -21756,7 +21679,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -21767,7 +21689,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "requires": { "brace-expansion": "^1.1.7" } @@ -21778,15 +21699,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "peer": true + "dev": true }, "@humanwhocodes/object-schema": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true, - "peer": true + "dev": true }, "@isaacs/cliui": { "version": "8.0.2", @@ -23931,7 +23850,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "peer": true, "requires": {} }, "acorn-walk": { @@ -23956,7 +23874,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -25902,8 +25819,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "peer": true + "dev": true }, "deepmerge": { "version": "4.3.1", @@ -26059,7 +25975,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "peer": true, "requires": { "esutils": "^2.0.2" } @@ -26439,7 +26354,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -26486,7 +26400,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "peer": true, "requires": { "color-convert": "^2.0.1" } @@ -26495,15 +26408,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "peer": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "peer": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -26514,7 +26425,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -26525,7 +26435,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "peer": true, "requires": { "color-name": "~1.1.4" } @@ -26534,22 +26443,19 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "peer": true + "dev": true }, "eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "peer": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -26559,22 +26465,19 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "peer": true + "dev": true }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true + "dev": true }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "peer": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -26585,7 +26488,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "peer": true, "requires": { "type-fest": "^0.20.2" } @@ -26594,15 +26496,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true + "dev": true }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "peer": true, "requires": { "argparse": "^2.0.1" } @@ -26612,7 +26512,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "peer": true, "requires": { "p-locate": "^5.0.0" } @@ -26622,7 +26521,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, "requires": { "brace-expansion": "^1.1.7" } @@ -26632,7 +26530,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "peer": true, "requires": { "p-limit": "^3.0.2" } @@ -26642,7 +26539,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "peer": true, "requires": { "has-flag": "^4.0.0" } @@ -26651,8 +26547,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -27023,7 +26918,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "peer": true, "requires": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -27034,8 +26928,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "peer": true + "dev": true } } }, @@ -27050,7 +26943,6 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "peer": true, "requires": { "estraverse": "^5.1.0" }, @@ -27059,8 +26951,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -27068,7 +26959,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "peer": true, "requires": { "estraverse": "^5.2.0" }, @@ -27076,8 +26966,7 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "peer": true + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" } } }, @@ -27288,8 +27177,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "peer": true + "dev": true }, "fast-uri": { "version": "3.0.1", @@ -27298,9 +27186,9 @@ "dev": true }, "fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "peer": true, "requires": { @@ -27347,7 +27235,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "peer": true, "requires": { "flat-cache": "^3.0.4" } @@ -27482,7 +27369,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, - "peer": true, "requires": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -27493,8 +27379,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "peer": true + "dev": true }, "floating-vue": { "version": "1.0.0-beta.19", @@ -27731,7 +27616,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "peer": true, "requires": { "is-glob": "^4.0.3" } @@ -27841,8 +27725,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "peer": true + "dev": true }, "handle-thing": { "version": "2.0.1", @@ -28271,7 +28154,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "peer": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -28281,8 +28163,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "peer": true + "dev": true } } }, @@ -28586,8 +28467,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "peer": true + "dev": true }, "is-plain-obj": { "version": "1.1.0", @@ -30216,8 +30096,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "peer": true + "dev": true }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -30227,15 +30106,13 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "peer": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "peer": true + "dev": true }, "json5": { "version": "2.2.3", @@ -30248,7 +30125,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "peer": true, "requires": { "json-buffer": "3.0.1" } @@ -30295,7 +30171,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "peer": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -30376,8 +30251,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "dev": true }, "lodash.throttle": { "version": "4.1.1", @@ -31511,7 +31385,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "peer": true, "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -31592,7 +31465,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "peer": true, "requires": { "callsites": "^3.0.0" } @@ -31915,8 +31787,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "peer": true + "dev": true }, "prettier": { "version": "2.8.8", @@ -32569,7 +32440,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "peer": true, "requires": { "glob": "^7.1.3" } @@ -33886,8 +33756,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "peer": true + "dev": true }, "thunky": { "version": "1.1.0", @@ -34170,7 +34039,6 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "peer": true, "requires": { "prelude-ls": "^1.2.1" } @@ -34404,7 +34272,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "peer": true, "requires": { "punycode": "^2.1.0" } @@ -35012,8 +34879,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "peer": true + "dev": true }, "wrap-ansi": { "version": "7.0.0", diff --git a/package.json b/package.json index a81bc78b..ebe52060 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@nextcloud/l10n": "^2.0.1", "@nextcloud/router": "^2.0.1", "@nextcloud/vue": "^8.12.0", + "@vueuse/core": "^10.11.0", "apexcharts": "^3.50.0", "bootstrap-vue": "^2.23.1", "css-loader": "^6.8.1", @@ -53,6 +54,7 @@ "@types/node": "^20.14.12", "@vue/test-utils": "^2.4.6", "@vue/vue2-jest": "^29.0.0", + "eslint": "^8.57.0", "eslint-webpack-plugin": "^4.2.0", "jest": "^29.0.0", "jest-environment-jsdom": "^29.7.0", diff --git a/src/modals/attachment/AddAttachmentModal.vue b/src/modals/attachment/AddAttachmentModal.vue index bd339333..533c3f8f 100644 --- a/src/modals/attachment/AddAttachmentModal.vue +++ b/src/modals/attachment/AddAttachmentModal.vue @@ -42,6 +42,33 @@ import { navigationStore, publicationStore } from '../../store/store.js' label="Download URL" maxlength="255" :value.sync="publicationStore.attachmentItem.downloadURL" /> +
+ + + Bestand toevoegen + + + + + + {{ file }} + + + +
{{ files && Object.values(files)[0] }}
+
+ + + + + + diff --git a/src/store/modules/configuration.specs.js b/src/store/modules/configuration.specs.js deleted file mode 100644 index 085b544a..00000000 --- a/src/store/modules/configuration.specs.js +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint-disable no-console */ -import { createPinia, setActivePinia } from 'pinia' - -import { Configuration } from '../../entities/index.js' -import { useConfigurationStore } from './configuration.js' - -describe( - 'Metadata Store', () => { - beforeEach( - () => { - setActivePinia(createPinia()) - }, - ) - - it( - 'sets configuration item correctly', () => { - const store = useConfigurationStore() - - store.setConfigurationItem(testData[0]) - - expect(store.configurationItem).toBeInstanceOf(Configuration) - expect(store.configurationItem).toEqual(testData[0]) - expect(store.configurationItem.validate()).toBe(true) - - store.setConfigurationItem(testData[1]) - - expect(store.configurationItem).toBeInstanceOf(Configuration) - expect(store.configurationItem).not.toEqual(testData[1]) - expect(store.configurationItem.validate()).toBe(true) - - store.setConfigurationItem(testData[2]) - - expect(store.configurationItem).toBeInstanceOf(Configuration) - expect(store.configurationItem).toEqual(testData[2]) - expect(store.configurationItem.validate()).toBe(false) - }, - ) - - it( - 'sets configuration list correctly', () => { - const store = useConfigurationStore() - - store.setConfigurationItem(testData) - - expect(store.configurationList).toHaveLength(testData.length) - - expect(store.configurationList[0]).toBeInstanceOf(Configuration) - expect(store.configurationList[0]).toEqual(testData[0]) - expect(store.configurationList[0].validate()).toBe(true) - - expect(store.configurationList[1]).toBeInstanceOf(Configuration) - expect(store.configurationList[1]).not.toEqual(testData[1]) - expect(store.configurationList[1].validate()).toBe(true) - - expect(store.configurationList[2]).toBeInstanceOf(Configuration) - expect(store.configurationList[2]).toEqual(testData[2]) - expect(store.configurationList[2].validate()).toBe(false) - }, - ) - }, -) - -const testData = [ - { // full data - id: '1', - reference: 'ref1', - title: 'test 1', - summary: 'a short form summary', - description: 'a really really long description about this catalogus', - image: 'https://example.com/image.jpg', - category: 'category1', - portal: 'portal1', - catalogi: 'catalogi1', - metaData: 'meta1', - publicationDate: '2024-01-01', - modified: '2024-01-02', - featured: true, - organisation: [{ name: 'Org1' }], - data: [{ key: 'value1' }], - attachments: ['attachment1'], - attachmentCount: 1, - schema: 'schema1', - status: 'status1', - license: 'MIT', - themes: 'theme1', - anonymization: { anonymized: 'yes', results: 'success' }, - language: { code: 'en', level: 'native' }, - }, - { // partial data - id: '2', - reference: 'ref2', - title: 'test 2', - summary: 'a short form summary', - description: 'a really really long description about this catalogus', - image: 'https://example.com/image.jpg', - category: 'category2', - portal: 'portal2', - catalogi: 'catalogi2', - metaData: 'meta2', - publicationDate: '2024-01-01', - modified: '2024-01-02', - featured: true, - organisation: [{ name: 'Org1' }], - data: [{ key: 'value1' }], - attachments: ['attachment1'], - attachmentCount: 1, - - themes: 'theme1', - anonymization: { anonymized: 'yes', results: 'success' }, - language: { code: 'en', level: 'native' }, - }, - { // invalid data - id: '3', - reference: 'ref3', - title: '', - summary: 'a short form summary', - description: 'a really really long description about this catalogus', - image: 'https://example.com/image.jpg', - category: 'category3', - portal: 'portal3', - catalogi: 'catalogi3', - metaData: 'meta3', - publicationDate: '2024-01-01', - modified: '2024-01-02', - featured: true, - organisation: [{ name: 'Org1' }], - data: [{ key: 'value1' }], - attachments: ['attachment1'], - attachmentCount: 1, - schema: 'schema1', - status: 'status1', - license: 'MIT', - themes: 'theme1', - anonymization: { anonymized: 'yes', results: 'success' }, - language: { code: 'en', level: 'native' }, - }, -] diff --git a/src/views/catalogi/CatalogiDetails.vue b/src/views/catalogi/CatalogiDetails.vue index bdc03ef6..7fd2afad 100644 --- a/src/views/catalogi/CatalogiDetails.vue +++ b/src/views/catalogi/CatalogiDetails.vue @@ -41,6 +41,12 @@ import { catalogiStore, navigationStore } from '../../store/store.js' Catalogus bekijken + + + Metadata toevoegen +