From f88e5ececb7eba585ab4c563e7561136003b96ab Mon Sep 17 00:00:00 2001 From: Marcus Eibrink-Lunzenauer Date: Tue, 4 Jul 2023 10:03:28 +0000 Subject: Add stock images, closes #2482 Closes #2482 Merge request studip/studip!1788 --- app/controllers/stock_images.php | 37 + app/views/stock_images/index.php | 1 + cli/studip | 2 +- composer.json | 3 +- composer.lock | 272 +++-- db/migrations/5.4.7_create_stock_images_table.php | 41 + ...5.4.8_add_image_type_to_structural_elements.php | 28 + lib/classes/JsonApi/RouteMap.php | 12 + .../Courseware/StructuralElementsImageDelete.php | 4 +- .../Courseware/StructuralElementsImageUpload.php | 3 +- .../Routes/Courseware/StructuralElementsUpdate.php | 78 +- .../JsonApi/Routes/StockImages/Authority.php | 57 ++ .../Routes/StockImages/StockImagesCreate.php | 101 ++ .../Routes/StockImages/StockImagesDelete.php | 32 + .../Routes/StockImages/StockImagesIndex.php | 33 + .../JsonApi/Routes/StockImages/StockImagesShow.php | 29 + .../Routes/StockImages/StockImagesUpdate.php | 96 ++ .../Routes/StockImages/StockImagesUpload.php | 146 +++ .../Routes/StockImages/ValidationHelpers.php | 26 + lib/classes/JsonApi/SchemaMap.php | 1 + lib/classes/JsonApi/Schemas/StockImage.php | 54 + lib/classes/StockImages/PaletteCreator.php | 19 + lib/classes/StockImages/Scaler.php | 70 ++ lib/models/Courseware/BlockTypes/Headline.json | 3 + lib/models/Courseware/StructuralElement.php | 113 ++- lib/models/StockImage.php | 118 +++ lib/navigation/ContentsNavigation.php | 7 + package-lock.json | 1047 +++++++------------- package.json | 2 + public/assets/images/checkered-background.png | Bin 0 -> 89 bytes public/pictures/stock-images/.gitkeep | 0 .../assets/javascripts/bootstrap/stock-images.js | 5 + resources/assets/javascripts/entry-base.js | 1 + resources/assets/javascripts/lib/stock-images.js | 23 + resources/vue/components/ActiveFilter.vue | 54 + resources/vue/components/SearchWithFilter.vue | 157 +++ .../courseware/CoursewareFileChooser.vue | 6 + .../courseware/CoursewareHeadlineBlock.vue | 147 ++- .../courseware/CoursewareSearchResults.vue | 4 +- .../courseware/CoursewareSearchWidget.vue | 6 +- .../courseware/CoursewareShelfDialogAdd.vue | 74 +- .../courseware/CoursewareStructuralElement.vue | 127 ++- .../courseware/CoursewareUnitItemDialogLayout.vue | 4 +- .../vue/components/stock-images/ActionsWidget.vue | 35 + .../components/stock-images/AttributesFieldset.vue | 74 ++ .../components/stock-images/ColorFilterWidget.vue | 77 ++ .../vue/components/stock-images/EditDialog.vue | 97 ++ .../vue/components/stock-images/ImagesList.vue | 190 ++++ .../vue/components/stock-images/ImagesListItem.vue | 162 +++ .../components/stock-images/ImagesPagination.vue | 58 ++ .../vue/components/stock-images/MetadataBox.vue | 92 ++ .../components/stock-images/NavigationWidget.vue | 31 + .../stock-images/OrientationFilterWidget.vue | 47 + resources/vue/components/stock-images/Page.vue | 157 +++ .../vue/components/stock-images/SearchWidget.vue | 77 ++ .../stock-images/SelectableImageCard.vue | 48 + resources/vue/components/stock-images/Selector.vue | 68 ++ .../vue/components/stock-images/SelectorDialog.vue | 67 ++ .../vue/components/stock-images/SelectorSearch.vue | 162 +++ .../vue/components/stock-images/TagsInput.vue | 90 ++ .../vue/components/stock-images/Thumbnail.vue | 45 + .../vue/components/stock-images/ThumbnailCard.vue | 79 ++ .../vue/components/stock-images/UploadBox.vue | 76 ++ .../vue/components/stock-images/UploadDialog.vue | 91 ++ resources/vue/components/stock-images/colors.js | 18 + .../vue/components/stock-images/components.js | 21 + resources/vue/components/stock-images/filters.js | 71 ++ resources/vue/components/stock-images/format.js | 14 + resources/vue/courseware-index-app.js | 3 + resources/vue/courseware-shelf-app.js | 4 + resources/vue/plugins/stock-images.js | 49 + .../store/courseware/courseware-shelf.module.js | 9 + .../vue/store/courseware/courseware.module.js | 9 + resources/vue/store/stock-images.js | 64 ++ 74 files changed, 4184 insertions(+), 944 deletions(-) create mode 100644 app/controllers/stock_images.php create mode 100644 app/views/stock_images/index.php create mode 100644 db/migrations/5.4.7_create_stock_images_table.php create mode 100644 db/migrations/5.4.8_add_image_type_to_structural_elements.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/Authority.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/StockImagesCreate.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/StockImagesIndex.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/StockImagesShow.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php create mode 100644 lib/classes/JsonApi/Routes/StockImages/ValidationHelpers.php create mode 100644 lib/classes/JsonApi/Schemas/StockImage.php create mode 100644 lib/classes/StockImages/PaletteCreator.php create mode 100644 lib/classes/StockImages/Scaler.php create mode 100644 lib/models/StockImage.php create mode 100644 public/assets/images/checkered-background.png create mode 100644 public/pictures/stock-images/.gitkeep create mode 100644 resources/assets/javascripts/bootstrap/stock-images.js create mode 100644 resources/assets/javascripts/lib/stock-images.js create mode 100644 resources/vue/components/ActiveFilter.vue create mode 100644 resources/vue/components/SearchWithFilter.vue create mode 100644 resources/vue/components/stock-images/ActionsWidget.vue create mode 100644 resources/vue/components/stock-images/AttributesFieldset.vue create mode 100644 resources/vue/components/stock-images/ColorFilterWidget.vue create mode 100644 resources/vue/components/stock-images/EditDialog.vue create mode 100644 resources/vue/components/stock-images/ImagesList.vue create mode 100644 resources/vue/components/stock-images/ImagesListItem.vue create mode 100644 resources/vue/components/stock-images/ImagesPagination.vue create mode 100644 resources/vue/components/stock-images/MetadataBox.vue create mode 100644 resources/vue/components/stock-images/NavigationWidget.vue create mode 100644 resources/vue/components/stock-images/OrientationFilterWidget.vue create mode 100644 resources/vue/components/stock-images/Page.vue create mode 100644 resources/vue/components/stock-images/SearchWidget.vue create mode 100644 resources/vue/components/stock-images/SelectableImageCard.vue create mode 100644 resources/vue/components/stock-images/Selector.vue create mode 100644 resources/vue/components/stock-images/SelectorDialog.vue create mode 100644 resources/vue/components/stock-images/SelectorSearch.vue create mode 100644 resources/vue/components/stock-images/TagsInput.vue create mode 100644 resources/vue/components/stock-images/Thumbnail.vue create mode 100644 resources/vue/components/stock-images/ThumbnailCard.vue create mode 100644 resources/vue/components/stock-images/UploadBox.vue create mode 100644 resources/vue/components/stock-images/UploadDialog.vue create mode 100644 resources/vue/components/stock-images/colors.js create mode 100644 resources/vue/components/stock-images/components.js create mode 100644 resources/vue/components/stock-images/filters.js create mode 100644 resources/vue/components/stock-images/format.js create mode 100644 resources/vue/plugins/stock-images.js create mode 100644 resources/vue/store/stock-images.js diff --git a/app/controllers/stock_images.php b/app/controllers/stock_images.php new file mode 100644 index 0000000..b272a71 --- /dev/null +++ b/app/controllers/stock_images.php @@ -0,0 +1,37 @@ +check('admin'); + + if (Navigation::hasItem('/contents/stock_images')) { + Navigation::activateItem('/contents/stock_images'); + } + \PageLayout::setTitle(_('Verwaltung des Bilder-Pools')); + $this->setSidebar(); + } + + /** + * Administration view for banner + */ + public function index_action(): void + { + } + + /** + * Setup the sidebar + */ + protected function setSidebar(): void + { + $sidebar = \Sidebar::Get(); + $sidebar->addWidget(new \VueWidget('stock-images-widget')); + } +} diff --git a/app/views/stock_images/index.php b/app/views/stock_images/index.php new file mode 100644 index 0000000..d07c9d7 --- /dev/null +++ b/app/views/stock_images/index.php @@ -0,0 +1 @@ +
diff --git a/cli/studip b/cli/studip index d830e31..ced9f0d 100755 --- a/cli/studip +++ b/cli/studip @@ -65,7 +65,7 @@ $commands = [ Commands\User\GetUser::class, ]; $creator = function ($command) { - return new $command(); + return app($command); }; $application->addCommands(array_map($creator, $commands)); $application->run(); diff --git a/composer.json b/composer.json index 101d770..413e23c 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,8 @@ "symfony/polyfill-php74": "^1.27", "symfony/polyfill-php80": "^1.27", "symfony/polyfill-php81": "^1.27", - "phpowermove/docblock": "^2.0" + "phpowermove/docblock": "^2.0", + "ksubileau/color-thief-php": "^2.0" }, "replace": { "symfony/polyfill-php54": "*", diff --git a/composer.lock b/composer.lock index 5119b01..a6d7622 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b208b63a6fc53d453c263420fa7350da", + "content-hash": "c4da295f5930f9bfa0b1c4014a12f5b3", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -413,22 +413,22 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.4", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", - "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "provide": { @@ -448,9 +448,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { @@ -512,7 +509,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.4" + "source": "https://github.com/guzzle/psr7/tree/2.5.0" }, "funding": [ { @@ -528,7 +525,7 @@ "type": "tidelift" } ], - "time": "2023-03-09T13:19:02+00:00" + "time": "2023-04-17T16:11:26+00:00" }, { "name": "jakeasmith/http_build_url", @@ -569,16 +566,16 @@ }, { "name": "jasig/phpcas", - "version": "1.5.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/apereo/phpCAS.git", - "reference": "d6f5797fb568726f34c8e48741776d81e4a2646b" + "reference": "c129708154852656aabb13d8606cd5b12dbbabac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/apereo/phpCAS/zipball/d6f5797fb568726f34c8e48741776d81e4a2646b", - "reference": "d6f5797fb568726f34c8e48741776d81e4a2646b", + "url": "https://api.github.com/repos/apereo/phpCAS/zipball/c129708154852656aabb13d8606cd5b12dbbabac", + "reference": "c129708154852656aabb13d8606cd5b12dbbabac", "shasum": "" }, "require": { @@ -599,6 +596,9 @@ } }, "autoload": { + "files": [ + "source/CAS.php" + ], "classmap": [ "source/" ] @@ -631,9 +631,9 @@ ], "support": { "issues": "https://github.com/apereo/phpCAS/issues", - "source": "https://github.com/apereo/phpCAS/tree/1.5.0" + "source": "https://github.com/apereo/phpCAS/tree/1.6.1" }, - "time": "2022-05-03T21:12:54+00:00" + "time": "2023-02-19T19:52:35+00:00" }, { "name": "jumbojett/openid-connect-php", @@ -678,6 +678,66 @@ "time": "2022-09-30T12:34:46+00:00" }, { + "name": "ksubileau/color-thief-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/ksubileau/color-thief-php.git", + "reference": "a1378533433dae824c2e5b70359f8b6ae16b1deb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ksubileau/color-thief-php/zipball/a1378533433dae824c2e5b70359f8b6ae16b1deb", + "reference": "a1378533433dae824c2e5b70359f8b6ae16b1deb", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "phpstan/phpstan": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpunit/phpunit": "^8.5" + }, + "suggest": { + "ext-gd": "to use the GD image adapter.", + "ext-gmagick": "to use the Gmagick image adapter.", + "ext-imagick": "to use the Imagick image adapter." + }, + "type": "library", + "autoload": { + "psr-4": { + "ColorThief\\": "src/ColorThief" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Subileau", + "homepage": "http://www.kevinsubileau.fr" + } + ], + "description": "Grabs the dominant color or a representative color palette from an image.", + "homepage": "http://www.kevinsubileau.fr/projets/color-thief-php", + "keywords": [ + "color", + "dominant", + "palette", + "php", + "thief" + ], + "support": { + "issues": "https://github.com/ksubileau/color-thief-php/issues", + "source": "https://github.com/ksubileau/color-thief-php/tree/v2.0.1" + }, + "time": "2022-11-12T10:09:40+00:00" + }, + { "name": "kub-at/php-simple-html-dom-parser", "version": "1.9.1", "source": { @@ -2279,21 +2339,21 @@ }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -2313,7 +2373,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -2328,31 +2388,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "1.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -2381,27 +2441,27 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:50:52+00:00" }, { "name": "psr/http-server-handler", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", "shasum": "" }, "require": { "php": ">=7.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -2421,7 +2481,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP server-side request handler", @@ -2437,28 +2497,27 @@ "server" ], "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" }, - "time": "2018-10-30T16:46:14+00:00" + "time": "2023-04-10T20:06:20+00:00" }, { "name": "psr/http-server-middleware", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-server-middleware.git", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5" + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5", - "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", "shasum": "" }, "require": { "php": ">=7.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0 || ^2.0", "psr/http-server-handler": "^1.0" }, "type": "library", @@ -2479,7 +2538,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP server-side middleware", @@ -2495,9 +2554,9 @@ ], "support": { "issues": "https://github.com/php-fig/http-server-middleware/issues", - "source": "https://github.com/php-fig/http-server-middleware/tree/master" + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" }, - "time": "2018-10-30T17:12:04+00:00" + "time": "2023-04-11T06:14:47+00:00" }, { "name": "psr/log", @@ -2945,16 +3004,16 @@ }, { "name": "symfony/console", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9" + "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c77433ddc6cdc689caf48065d9ea22ca0853fbd9", - "reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9", + "url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8", + "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8", "shasum": "" }, "require": { @@ -3019,12 +3078,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.21" + "source": "https://github.com/symfony/console/tree/v5.4.22" }, "funding": [ { @@ -3040,7 +3099,7 @@ "type": "tidelift" } ], - "time": "2023-02-25T16:59:41+00:00" + "time": "2023-03-25T09:27:28+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3679,16 +3738,16 @@ }, { "name": "symfony/process", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd" + "reference": "4b850da0cc3a2a9181c1ed407adbca4733dc839b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", - "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", + "url": "https://api.github.com/repos/symfony/process/zipball/4b850da0cc3a2a9181c1ed407adbca4733dc839b", + "reference": "4b850da0cc3a2a9181c1ed407adbca4733dc839b", "shasum": "" }, "require": { @@ -3721,7 +3780,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.21" + "source": "https://github.com/symfony/process/tree/v5.4.22" }, "funding": [ { @@ -3737,7 +3796,7 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2023-03-06T21:29:33+00:00" }, { "name": "symfony/service-contracts", @@ -3824,16 +3883,16 @@ }, { "name": "symfony/string", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f" + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/edac10d167b78b1d90f46a80320d632de0bd9f2f", - "reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f", + "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", "shasum": "" }, "require": { @@ -3890,7 +3949,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.21" + "source": "https://github.com/symfony/string/tree/v5.4.22" }, "funding": [ { @@ -3906,7 +3965,7 @@ "type": "tidelift" } ], - "time": "2023-02-22T08:00:55+00:00" + "time": "2023-03-14T06:11:53+00:00" }, { "name": "symfony/yaml", @@ -5223,34 +5282,29 @@ }, { "name": "php-http/httplug", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "f640739f80dfa1152533976e3c112477f69274eb" + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/f640739f80dfa1152533976e3c112477f69274eb", - "reference": "f640739f80dfa1152533976e3c112477f69274eb", + "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", "php-http/promise": "^1.1", "psr/http-client": "^1.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.1", - "phpspec/phpspec": "^5.1 || ^6.0" + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "psr-4": { "Http\\Client\\": "src/" @@ -5279,9 +5333,9 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.3.0" + "source": "https://github.com/php-http/httplug/tree/2.4.0" }, - "time": "2022-02-21T09:52:22+00:00" + "time": "2023-04-14T15:10:03+00:00" }, { "name": "php-http/promise", @@ -5342,16 +5396,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.8", + "version": "1.10.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0166aef76e066f0dd2adc2799bdadfa1635711e9" + "reference": "d232901b09e67538e5c86a724be841bea5768a7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0166aef76e066f0dd2adc2799bdadfa1635711e9", - "reference": "0166aef76e066f0dd2adc2799bdadfa1635711e9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", + "reference": "d232901b09e67538e5c86a724be841bea5768a7c", "shasum": "" }, "require": { @@ -5400,7 +5454,7 @@ "type": "tidelift" } ], - "time": "2023-03-24T10:28:16+00:00" + "time": "2023-04-19T13:47:27+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5848,21 +5902,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -5882,7 +5936,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -5894,9 +5948,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client/tree/1.0.2" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-04-10T20:12:12+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6695,16 +6749,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f0ae1383a8285dfc6752b8d8602790953118ff5a" + "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f0ae1383a8285dfc6752b8d8602790953118ff5a", - "reference": "f0ae1383a8285dfc6752b8d8602790953118ff5a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f", "shasum": "" }, "require": { @@ -6760,7 +6814,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.21" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.22" }, "funding": [ { @@ -6776,7 +6830,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-03-17T11:31:58+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6922,16 +6976,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.21", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74" + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", - "reference": "6c5ac3a1be8b849d59a1a77877ee110e1b55eb74", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", + "reference": "e2edac9ce47e6df07e38143c7cfa6bdbc1a6dcc4", "shasum": "" }, "require": { @@ -6991,7 +7045,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.21" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.22" }, "funding": [ { @@ -7007,7 +7061,7 @@ "type": "tidelift" } ], - "time": "2023-02-23T10:00:28+00:00" + "time": "2023-03-25T09:27:28+00:00" }, { "name": "theseer/tokenizer", @@ -7146,5 +7200,5 @@ "platform-overrides": { "php": "7.2.5" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/db/migrations/5.4.7_create_stock_images_table.php b/db/migrations/5.4.7_create_stock_images_table.php new file mode 100644 index 0000000..e1a7b04 --- /dev/null +++ b/db/migrations/5.4.7_create_stock_images_table.php @@ -0,0 +1,41 @@ +exec($query); + } + + public function down() + { + $db = DBManager::get(); + $db->exec('DROP TABLE IF EXISTS `stock_images`'); + } +} diff --git a/db/migrations/5.4.8_add_image_type_to_structural_elements.php b/db/migrations/5.4.8_add_image_type_to_structural_elements.php new file mode 100644 index 0000000..7a2600a --- /dev/null +++ b/db/migrations/5.4.8_add_image_type_to_structural_elements.php @@ -0,0 +1,28 @@ +exec( + sprintf( + 'ALTER TABLE `cw_structural_elements` ' . + 'ADD `image_type` ENUM("%s", "%s") NOT NULL DEFAULT "%1$s" AFTER `image_id`', + \FileRef::class, + \StockImage::class + ) + ); + } + + public function down() + { + $db = DBManager::get(); + $db->exec('ALTER TABLE `cw_structural_elements` DROP `image_type`'); + } +} diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index f01a096..9d94c19 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -132,6 +132,7 @@ class RouteMap $this->addAuthenticatedLtiRoutes($group); $this->addAuthenticatedMessagesRoutes($group); $this->addAuthenticatedNewsRoutes($group); + $this->addAuthenticatedStockImagesRoutes($group); $this->addAuthenticatedStudyAreasRoutes($group); $this->addAuthenticatedTreeRoutes($group); $this->addAuthenticatedWikiRoutes($group); @@ -597,6 +598,17 @@ class RouteMap $group->delete('/forum-entries/{id}', Routes\Forum\ForumEntriesDelete::class); } + private function addAuthenticatedStockImagesRoutes(RouteCollectorProxy $group): void + { + $group->get('/stock-images', Routes\StockImages\StockImagesIndex::class); + $group->post('/stock-images', Routes\StockImages\StockImagesCreate::class); + $group->get('/stock-images/{id}', Routes\StockImages\StockImagesShow::class); + $group->patch('/stock-images/{id}', Routes\StockImages\StockImagesUpdate::class); + $group->delete('/stock-images/{id}', Routes\StockImages\StockImagesDelete::class); + + $group->post('/stock-images/{id}/blob', Routes\StockImages\StockImagesUpload::class); + } + private function addRelationship(RouteCollectorProxy $group, string $url, string $handler): void { $group->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler); diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageDelete.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageDelete.php index b8d2a0c..8954917 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageDelete.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageDelete.php @@ -21,11 +21,13 @@ class StructuralElementsImageDelete extends NonJsonApiController throw new AuthorizationFailedException(); } - if ($structuralElement->image) { + // remove existing image + if (is_a($structuralElement->image, \FileRef::class)) { $structuralElement->image->getFileType()->delete(); } $structuralElement->image_id = null; + $structuralElement->image_type = \FileRef::class; $structuralElement->store(); return $response->withStatus(204); diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php index 3f85d9b..aaca497 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsImageUpload.php @@ -34,12 +34,13 @@ class StructuralElementsImageUpload extends NonJsonApiController $fileRef = $this->handleUpload($request, $publicFolder, $structuralElement); // remove existing image - if ($structuralElement->image) { + if (is_a($structuralElement->image, \FileRef::class)) { $structuralElement->image->getFileType()->delete(); } // refer to newly uploaded image $structuralElement->image_id = $fileRef->id; + $structuralElement->image_type = \FileRef::class; $structuralElement->store(); return $response->withStatus(201); diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php index febe3cc..6bf0e79 100644 --- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsUpdate.php @@ -8,6 +8,8 @@ use JsonApi\Errors\RecordNotFoundException; use JsonApi\JsonApiController; use JsonApi\Routes\ValidationTrait; use JsonApi\Schemas\Courseware\StructuralElement as StructuralElementSchema; +use JsonApi\Schemas\FileRef as FileRefSchema; +use JsonApi\Schemas\StockImage as StockImageSchema; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; @@ -83,6 +85,17 @@ class StructuralElementsUpdate extends JsonApiController } } } + + $imageRelationship = 'data.relationships.' . StructuralElementSchema::REL_IMAGE; + if (self::arrayHas($json, $imageRelationship)) { + $relation = self::arrayGet($json, $imageRelationship); + if (isset($relation['data']['type'])) { + $validTypes = [FileRefSchema::TYPE, StockImageSchema::TYPE]; + if (!in_array($relation['data']['type'], $validTypes)) { + return 'Relationship `image` can only be of type ' . join(', ', $validTypes); + } + } + } } private function getParentFromJson($json) @@ -114,7 +127,7 @@ class StructuralElementsUpdate extends JsonApiController foreach ($attributes as $jsonKey) { $sormKey = strtr($jsonKey, '-', '_'); - if ($val = self::arrayGet($json, 'data.attributes.'.$jsonKey, '')) { + if ($val = self::arrayGet($json, 'data.attributes.' . $jsonKey, '')) { $resource->$sormKey = $val; } } @@ -133,10 +146,73 @@ class StructuralElementsUpdate extends JsonApiController $resource->parent_id = $parent->id; } + // update image + $this->updateImage($resource, $json); + $resource->editor_id = $user->id; $resource->store(); return $resource; }); } + + private function updateImage(StructuralElement $resource, array $json): void + { + if (!$this->imageNeedsUpdate($resource, $json)) { + return; + } + + $currentImage = $resource->image; + list($imageType, $imageId) = $this->getImageRelationshipData($json); + + // remove current image + if (!$imageType && !$imageId) { + if (is_a($currentImage, \FileRef::class)) { + $currentImage->getFileType()->delete(); + } + $resource->image_id = null; + $resource->image_type = null; + } elseif ($imageType === StockImageSchema::TYPE) { + $stockImageExists = \StockImage::countBySQL('id = ?', [$imageId]); + if (!$stockImageExists) { + throw new RecordNotFoundException('Could not find that stock image.'); + } + $resource->image_id = $imageId; + $resource->image_type = \StockImage::class; + } elseif ($imageType === FileRefSchema::TYPE) { + throw new \RuntimeException('Not yet implemented.'); + } + } + + private function getImageRelationshipData(array $json): array + { + $imageRelationship = 'data.relationships.' . StructuralElementSchema::REL_IMAGE; + if (!self::arrayHas($json, $imageRelationship)) { + throw new \RuntimeException('Missing relationship `image`'); + } + $relation = self::arrayGet($json, $imageRelationship); + + return [self::arrayGet($relation, 'data.type'), self::arrayGet($relation, 'data.id')]; + } + + private function imageNeedsUpdate(StructuralElement $resource, array $json): bool + { + $imageRelationship = 'data.relationships.' . StructuralElementSchema::REL_IMAGE; + if (!self::arrayHas($json, $imageRelationship)) { + return false; + } + + $currentImage = $resource->image; + list($imageType, $imageId) = $this->getImageRelationshipData($json); + + if (!$currentImage) { + return (bool) $imageId; + } + + $currentImageSchema = $this->getSchema($currentImage); + + return ($currentImage && !$imageId) + || $currentImageSchema::TYPE !== $imageType + || $currentImage->id != $imageId; + } } diff --git a/lib/classes/JsonApi/Routes/StockImages/Authority.php b/lib/classes/JsonApi/Routes/StockImages/Authority.php new file mode 100644 index 0000000..77851fd --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/Authority.php @@ -0,0 +1,57 @@ +have_perm('admin', $user->id); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canUpdateStockImage(User $user, StockImage $resource): bool + { + return self::canCreateStockImage($user); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canUploadStockImage(User $user, StockImage $resource): bool + { + return self::canCreateStockImage($user); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public static function canDeleteStockImage(User $user, StockImage $resource): bool + { + return self::canCreateStockImage($user); + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesCreate.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesCreate.php new file mode 100644 index 0000000..5c11a9e --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesCreate.php @@ -0,0 +1,101 @@ +validate($request, $resource); + $user = $this->getUser($request); + if (!Authority::canCreateStockImage($user, $resource)) { + throw new AuthorizationFailedException(); + } + $resource = $this->createResource($json); + + return $this->getContentResponse($resource); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function validateResourceDocument($json, $data) + { + if (!self::arrayHas($json, 'data')) { + return 'Missing `data` member at document´s top level.'; + } + + if (ResourceSchema::TYPE !== self::arrayGet($json, 'data.type')) { + return 'Wrong `type` member of document´s `data`.'; + } + + $errors = iterator_to_array(self::requiredAttributes($json, ['title', 'description', 'author', 'license'])); + if (!empty($errors)) { + return current($errors); + } + + // optional attribute `tags` has to be an array + if (self::arrayHas($json, 'data.attributes.tags')) { + $tags = self::arrayGet($json, 'data.attributes.tags', []); + if (!is_array($tags) || !array_is_list($tags)) { + return 'Attribute `tags` has to be a list of strings.'; + } + } + } + + protected static function requiredAttributes($json, $keys) + { + foreach ($keys as $key) { + $path = 'data.attributes.' . $key; + $value = self::arrayGet($json, $path, ''); + if (empty($value)) { + yield sprintf('Missing or empty attribute `%s`', $key); + } + } + } + + private function createResource(array $json): StockImage + { + $resource = new StockImage(); + $resource->setData( + array_merge( + self::getAttributeDefaults(), + self::getAttributeUpdates($json, ['title', 'description', 'author', 'license']), + self::getTags($json) + ) + ); + $resource->store(); + + return $resource; + } + + private static function getAttributeDefaults(): iterable + { + return [ + 'height' => 0, + 'mime_type' => '', + 'size' => 0, + 'width' => 0, + 'tags' => '[]', + ]; + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php new file mode 100644 index 0000000..e0b5468 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesDelete.php @@ -0,0 +1,32 @@ +getUser($request), $resource)) { + throw new AuthorizationFailedException(); + } + $resource->delete(); + + return $this->getCodeResponse(204); + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesIndex.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesIndex.php new file mode 100644 index 0000000..8bd3516 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesIndex.php @@ -0,0 +1,33 @@ +getUser($request))) { + throw new AuthorizationFailedException(); + } + + list($offset, $limit) = $this->getOffsetAndLimit(); + $total = \StockImage::countBySQL('1'); + $stockImages = \StockImage::findBySQL("1 ORDER BY title ASC LIMIT {$offset}, {$limit}"); + + return $this->getPaginatedContentResponse($stockImages, $total); + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesShow.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesShow.php new file mode 100644 index 0000000..52e79e1 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesShow.php @@ -0,0 +1,29 @@ +getUser($request), $resource)) { + throw new AuthorizationFailedException(); + } + + return $this->getContentResponse($resource); + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php new file mode 100644 index 0000000..cef9db5 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpdate.php @@ -0,0 +1,96 @@ +validate($request, $resource); + $user = $this->getUser($request); + if (!Authority::canUpdateStockImage($user, $resource)) { + throw new AuthorizationFailedException(); + } + $resource = $this->updateResource($resource, $json); + + return $this->getContentResponse($resource); + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function validateResourceDocument($json, $data) + { + if (!self::arrayHas($json, 'data')) { + return 'Missing `data` member at document´s top level.'; + } + + if (ResourceSchema::TYPE !== self::arrayGet($json, 'data.type')) { + return 'Wrong `type` member of document´s `data`.'; + } + + if (!self::arrayHas($json, 'data.id')) { + return 'Document must have an `id`.'; + } + + $errors = iterator_to_array(self::nonEmptyAttributes($json, ['title', 'description', 'author', 'license'])); + if (!empty($errors)) { + return current($errors); + } + + // optional attribute `tags` has to be an array + if (self::arrayHas($json, 'data.attributes.tags')) { + $tags = self::arrayGet($json, 'data.attributes.tags', []); + if (!is_array($tags) || !array_is_list($tags)) { + return 'Attribute `tags` has to be a list of strings.'; + } + } + } + + protected static function nonEmptyAttributes($json, $keys) + { + foreach ($keys as $key) { + $path = 'data.attributes.' . $key; + if (self::arrayHas($json, $path)) { + $value = self::arrayGet($json, $path); + if (empty($value)) { + yield sprintf('Attribute `%s` must not be empty', $key); + } + } + } + } + + private function updateResource(StockImage $resource, array $json): void + { + $updates = array_merge( + self::getAttributeUpdates($json, ['title', 'description', 'author', 'license']), + self::getTags($json) + ); + $resource->setData($updates); + $resource->store(); + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php new file mode 100644 index 0000000..b05b370 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/StockImagesUpload.php @@ -0,0 +1,146 @@ +getUser($request), $resource)) { + throw new AuthorizationFailedException(); + } + + $this->handleUpload($request, $resource); + $this->processStockImage($resource); + + return $this->redirectToStockImage($response, $resource); + } + + private function handleUpload(Request $request, \StockImage $resource): void + { + $uploadedFile = $this->getUploadedFile($request); + if (UPLOAD_ERR_OK !== $uploadedFile->getError()) { + $error = $this->getErrorString($uploadedFile->getError()); + throw new BadRequestException($error); + } + + $error = self::validate($uploadedFile); + if (!empty($error)) { + throw new BadRequestException($error); + } + + $resource->mime_type = $uploadedFile->getClientMediaType(); + $resource->size = $uploadedFile->getSize(); + $uploadedFile->moveTo($resource->getPath()); + + $imageSize = getimagesize($resource->getPath()); + $resource->width = $imageSize[0]; + $resource->height = $imageSize[1]; + + $resource->store(); + } + + private function getUploadedFile(Request $request): UploadedFile + { + $files = iterator_to_array($this->getUploadedFiles($request)); + + if (0 === count($files)) { + throw new BadRequestException('File upload required.'); + } + + if (count($files) > 1) { + throw new BadRequestException('Multiple file upload not possible.'); + } + + $uploadedFile = reset($files); + if (UPLOAD_ERR_OK !== $uploadedFile->getError()) { + throw new BadRequestException('Upload error.'); + } + + return $uploadedFile; + } + + /** + * @return iterable a list of uploaded files + */ + private function getUploadedFiles(Request $request): iterable + { + foreach ($request->getUploadedFiles() as $item) { + if (!is_array($item)) { + yield $item; + continue; + } + foreach ($item as $file) { + yield $file; + } + } + } + + private function getErrorString(int $errNo): string + { + $errors = [ + UPLOAD_ERR_OK => 'There is no error, the file uploaded with success', + UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', + UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', + UPLOAD_ERR_NO_FILE => 'No file was uploaded', + UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder', + UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', + UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', + ]; + + return $errors[$errNo] ?? ''; + } + + /** + * @return string|null null, if the file is valid, otherwise a string containing the error + */ + private function validate(UploadedFile $file) + { + $mimeType = $file->getClientMediaType(); + if (!in_array($mimeType, ['image/gif', 'image/jpeg', 'image/png', 'image/webp'])) { + return 'Unsupported media type.'; + } + } + + /** + * @SuppressWarnings(PHPMD.Superglobals) + */ + private function redirectToStockImage(Response $response, \StockImage $stockImage): Response + { + $pathinfo = $this->getSchema($stockImage) + ->getSelfLink($stockImage) + ->getStringRepresentation($this->container->get('json-api-integration-urlPrefix')); + $old = \URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']); + $url = \URLHelper::getURL($pathinfo, [], true); + \URLHelper::setBaseURL($old); + + return $response->withHeader('Location', $url)->withStatus(201); + } + + private function processStockImage(\StockImage $resource): void + { + $scaler = new Scaler(); + $scaler($resource); + $paletteCreator = new PaletteCreator(); + $paletteCreator($resource); + } +} diff --git a/lib/classes/JsonApi/Routes/StockImages/ValidationHelpers.php b/lib/classes/JsonApi/Routes/StockImages/ValidationHelpers.php new file mode 100644 index 0000000..b0cf1c6 --- /dev/null +++ b/lib/classes/JsonApi/Routes/StockImages/ValidationHelpers.php @@ -0,0 +1,26 @@ + json_encode(self::arrayGet($json, 'data.attributes.tags', []))]; + } +} diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php index e5a2f67..dd74bc9 100644 --- a/lib/classes/JsonApi/SchemaMap.php +++ b/lib/classes/JsonApi/SchemaMap.php @@ -40,6 +40,7 @@ class SchemaMap \SemType::class => Schemas\SemType::class, \SeminarCycleDate::class => Schemas\SeminarCycleDate::class, \Statusgruppen::class => Schemas\StatusGroup::class, + \StockImage::class => Schemas\StockImage::class, \JsonApi\Models\Studip::class => Schemas\Studip::class, \JsonApi\Models\StudipProperty::class => Schemas\StudipProperty::class, \StudipComment::class => Schemas\StudipComment::class, diff --git a/lib/classes/JsonApi/Schemas/StockImage.php b/lib/classes/JsonApi/Schemas/StockImage.php new file mode 100644 index 0000000..e28582c --- /dev/null +++ b/lib/classes/JsonApi/Schemas/StockImage.php @@ -0,0 +1,54 @@ +getId(); + } + + /** + * {@inheritdoc} + */ + public function getAttributes($resource, ContextInterface $context): iterable + { + return [ + 'title' => $resource['title'], + 'description' => $resource['description'], + 'author' => $resource['author'], + 'license' => $resource['license'], + 'mime-type' => $resource['mime_type'], + + 'download-urls' => $resource->getDownloadURLs(), + + 'size' => (int) $resource['size'], + 'width' => (int) $resource['width'], + 'height' => (int) $resource['height'], + 'palette' => empty($resource['palette']) ? null : json_decode($resource['palette']), + 'tags' => empty($resource['tags']) ? [] : json_decode($resource['tags']), + + 'mkdate' => date('c', $resource['mkdate']), + 'chdate' => date('c', $resource['chdate']), + ]; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getRelationships($resource, ContextInterface $context): iterable + { + return []; + } +} diff --git a/lib/classes/StockImages/PaletteCreator.php b/lib/classes/StockImages/PaletteCreator.php new file mode 100644 index 0000000..73386ef --- /dev/null +++ b/lib/classes/StockImages/PaletteCreator.php @@ -0,0 +1,19 @@ +getPath(\StockImage::SIZE_SMALL); + $palette = ColorThief::getPalette($sourceImage, 3); + $stockImage->palette = json_encode($palette); + $stockImage->store(); + } +} diff --git a/lib/classes/StockImages/Scaler.php b/lib/classes/StockImages/Scaler.php new file mode 100644 index 0000000..32f6c7a --- /dev/null +++ b/lib/classes/StockImages/Scaler.php @@ -0,0 +1,70 @@ + $width) { + if ($name !== \StockImage::SIZE_ORIGINAL) { + $this->scaleToWidth($stockImage, $name, $width); + } + } + } + + private function scaleToWidth(\StockImage $stockImage, string $sizeName, int $targetWidth): bool + { + $image = $this->createImage($stockImage); + $width = imagesx($image); + if ($width < $targetWidth) { + return false; + } + + $scaledImage = imagescale($image, $targetWidth); + imagedestroy($image); + + return $this->storeImage($stockImage, $scaledImage, $sizeName); + } + + /** + * @return resource the \GDImage created from the original image file + */ + private function createImage(\StockImage $stockImage) + { + $type = $stockImage->mime_type; + $lookup = [ + 'image/gif' => 'imagecreatefromgif', + 'image/jpeg' => 'imagecreatefromjpeg', + 'image/png' => 'imagecreatefrompng', + 'image/webp' => 'imagecreatefromwebp', + ]; + if (!isset($lookup[$type])) { + throw new \RuntimeException(_('Unsupported image type.')); + } + + return $lookup[$type]($stockImage->getPath()); + } + + /** + * @param resource $image the scaled image + */ + private function storeImage(\StockImage $stockImage, $image, string $sizeName): bool + { + $type = $stockImage->mime_type; + $lookup = [ + 'image/gif' => 'imagegif', + 'image/jpeg' => 'imagejpeg', + 'image/png' => 'imagepng', + 'image/webp' => 'imagewebp', + ]; + if (!isset($lookup[$type])) { + throw new \RuntimeException(_('Unsupported image type.')); + } + + return $lookup[$type]($image, $stockImage->getPath($sizeName)); + } +} diff --git a/lib/models/Courseware/BlockTypes/Headline.json b/lib/models/Courseware/BlockTypes/Headline.json index 1e759e4..5eab705 100644 --- a/lib/models/Courseware/BlockTypes/Headline.json +++ b/lib/models/Courseware/BlockTypes/Headline.json @@ -26,6 +26,9 @@ "background_image_id": { "type": "string" }, + "background_image_type": { + "type": "string" + }, "background_type": { "type": "string" }, diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php index aee01a2..d3c77fa 100644 --- a/lib/models/Courseware/StructuralElement.php +++ b/lib/models/Courseware/StructuralElement.php @@ -26,6 +26,7 @@ use User; * @property int $position database column * @property string $title database column * @property string $image_id database column + * @property string $image_type database column * @property string $purpose database column * @property \JSONArrayObject $payload database column * @property int $public database column @@ -45,7 +46,7 @@ use User; * @property \User $owner belongs_to User * @property \User $editor belongs_to User * @property ?\User $edit_blocker belongs_to User - * @property ?\FileRef $image has_one FileRef + * @property \FileRef|\StockImage|null $image has_one FileRef or StockImage * @property ?\Courseware\Task $task has_one Courseware\Task * @property \SimpleORMapCollection $comments has_many Courseware\StructuralElementComment * @property \SimpleORMapCollection $feedback has_many Courseware\StructuralElementFeedback @@ -119,13 +120,6 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject 'foreign_key' => 'edit_blocker_id', ]; - $config['has_one']['image'] = [ - 'class_name' => \FileRef::class, - 'foreign_key' => 'image_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['comments'] = [ 'class_name' => StructuralElementComment::class, 'assoc_foreign_key' => 'structural_element_id', @@ -142,9 +136,58 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject 'order_by' => 'ORDER BY chdate', ]; + $config['additional_fields']['image'] = [ + 'get' => 'getImage', + 'set' => 'setImage' + ]; + + $config['registered_callbacks']['before_delete'][] = 'cbBeforeDelete'; + parent::configure($config); } + public function cbBeforeDelete() + { + $image = $this->getImage(); + if (is_a($image, \FileRef::class)) { + $image->delete(); + } + } + + /** + * @return null|\FileRef|\StockImage + */ + public function getImage() + { + if (!$this->image_id) { + return null; + } + + if (!in_array($this->image_type, [\FileRef::class, \StockImage::class])) { + return null; + } + + return $this->image_type::find($this->image_id); + } + + /** + * @param $image null|\FileRef|\StockImage + */ + public function setImage($image): void + { + if (is_null($image)) { + $this->image_id = null; + } elseif (is_a($image, \FileRef)) { + $this->image_id = $image->getId(); + $this->image_type = \FileRef::class; + } elseif (is_a($image, \StockImage)) { + $this->image_id = $image->getId(); + $this->image_type = \StockImage::class; + } else { + throw \BadMethodCallException('Invalid argument to method ' . __METHOD__); + } + } + /** * Returns the root element of a courseware instance for a user. * @@ -762,11 +805,20 @@ SQL; /** * Returns the URL of the image associated to this structural element. * - * @return string the image URL, if it exists; an empty string otherwise + * @return string|null the image URL, if it exists */ public function getImageUrl() { - return $this->image ? $this->image->getDownloadURL() : null; + $image = $this->getImage(); + if ($image) { + if (is_a($image, \FileRef::class)) { + return $image->getDownloadURL(); + } elseif (is_a($image, \StockImage::class)) { + return $image->getDownloadURL(\StockImage::SIZE_SMALL); + } + } + + return null; } public static function getClipboardBackup(): string @@ -800,8 +852,9 @@ SQL; $element->store(); - $file_ref_id = $this->copyImage($user, $element); - $element->image_id = $file_ref_id; + $image_id = $this->copyImage($user, $element); + $element->image_id = $image_id; + $element->image_type = $this->image_type; $element->store(); $this->copyContainers($user, $element); @@ -830,7 +883,7 @@ SQL; } static $mapping = []; - $file_ref_id = $this->copyImage($user, $parent); + $image_id = $this->copyImage($user, $parent); $element = self::build([ 'parent_id' => $parent->id, @@ -843,7 +896,8 @@ SQL; 'purpose' => empty($purpose) ? $this->purpose : $purpose, 'position' => $parent->countChildren(), 'payload' => $this->payload, - 'image_id' => $file_ref_id, + 'image_id' => $image_id, + 'image_type' => $this->image_type, 'read_approval' => $parent->read_approval, 'write_approval' => $parent->write_approval ]); @@ -876,19 +930,27 @@ SQL; private function copyImage(User $user, StructuralElement $parent) : ?string { - $file_ref_id = null; - - /** @var ?\FileRef $original_file_ref */ - $original_file_ref = \FileRef::find($this->image_id); - if ($original_file_ref) { - $instance = new Instance($this->getCourseware($parent->range_id, $parent->range_type)); - $folder = \Courseware\Filesystem\PublicFolder::findOrCreateTopFolder($instance); - /** @var \FileRef $file_ref */ - $file_ref = \FileManager::copyFile($original_file_ref->getFileType(), $folder, $user); - $file_ref_id = $file_ref->id; + if ($this->image_type === \StockImage::class) { + return $this->image_id; + } + + if ($this->image_type === \FileRef::class) { + $file_ref_id = null; + + /** @var ?\FileRef $original_file_ref */ + $original_file_ref = \FileRef::find($this->image_id); + if ($original_file_ref) { + $instance = new Instance($this->getCourseware($parent->range_id, $parent->range_type)); + $folder = \Courseware\Filesystem\PublicFolder::findOrCreateTopFolder($instance); + /** @var \FileRef $file_ref */ + $file_ref = \FileManager::copyFile($original_file_ref->getFileType(), $folder, $user); + $file_ref_id = $file_ref->id; + } + + return $file_ref_id; } - return $file_ref_id; + return null; } public function merge(User $user, StructuralElement $target): StructuralElement @@ -896,6 +958,7 @@ SQL; // merge with target if (!$target->image_id) { $target->image_id = $this->copyImage($user, $target); + $target->image_type = $this->image_type; } if ($target->title === 'neue Seite' || $target->title === 'New page') { diff --git a/lib/models/StockImage.php b/lib/models/StockImage.php new file mode 100644 index 0000000..54d0992 --- /dev/null +++ b/lib/models/StockImage.php @@ -0,0 +1,118 @@ + -1, + self::SIZE_LARGE => 2400, + self::SIZE_MEDIUM => 1920, + self::SIZE_SMALL => 640, + ]; + } + + protected static function configure($config = []) + { + $config['db_table'] = 'stock_images'; + + $config['registered_callbacks']['after_delete'][] = function ($resource) { + if ($resource->hasFile()) { + foreach (array_keys(self::sizes()) as $sizeName) { + $path = $resource->getPath($sizeName); + if (file_exists($path)) { + unlink($path); + } + } + } + }; + + parent::configure($config); + } + + /** + * @SuppressWarnings(PHPMD.Superglobals) + */ + public function getPath(string $size = self::SIZE_ORIGINAL): string + { + return sprintf( + '%s/public/pictures/stock-images/%s', + $GLOBALS['STUDIP_BASE_PATH'], + $this->getFilename($size) + ); + } + + public function getFilename(string $size = self::SIZE_ORIGINAL): string + { + return sprintf( + '%d-%s.%s', + $this->id, + $size, + substr($this->mime_type, 6) + ); + } + + /** + * return string|null either a string containing the public URL to the file + * or null if there is still no such file + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + public function getDownloadURL(string $size = self::SIZE_ORIGINAL) + { + if (!$this->hasFile()) { + return null; + } + $sizes = self::sizes(); + if (!(isset($sizes[$size]) && $sizes[$size] <= $this->width)) { + return null; + } + + return sprintf( + '%spictures/stock-images/%s', + $GLOBALS['ABSOLUTE_URI_STUDIP'], + $this->getFilename($size) + ); + } + + /** + * @return iterable an associative array of sizes to URLs + */ + public function getDownloadURLs(): iterable + { + return array_filter( + array_reduce( + array_keys(self::sizes()), + function ($urls, $size) { + return array_merge($urls, [$size => $this->getDownloadURL($size)]); + }, + [] + ) + ); + } + + public function hasFile(): bool + { + return !empty($this->mime_type); + } +} diff --git a/lib/navigation/ContentsNavigation.php b/lib/navigation/ContentsNavigation.php index 119de6a..c206ef5 100644 --- a/lib/navigation/ContentsNavigation.php +++ b/lib/navigation/ContentsNavigation.php @@ -139,6 +139,13 @@ class ContentsNavigation extends Navigation $this->addSubNavigation('my_elearning', $elearning); } + if ($GLOBALS['perm']->have_perm('admin')) { + $pool = new Navigation(_('Bilder-Pool'), 'dispatch.php/stock_images', []); + $pool->setImage(Icon::create('picture')); + $pool->setDescription(_('Verwalten Sie den Pool frei verfügbarer Bilder.')); + $this->addSubNavigation('stock_images', $pool); + } + if (!$GLOBALS['perm']->have_perm('root') && $GLOBALS['user']->getAuthenticatedUser()->hasRole('Hilfe-Administrator(in)')) { $help = new Navigation(_('Hilfe'), 'dispatch.php/help_content/admin_overview'); $help->setImage(Icon::create('question-circle')); diff --git a/package-lock.json b/package-lock.json index 0b087b5..9a91eeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "@fullcalendar/resource-timegrid": "^4.3.0", "@fullcalendar/resource-timeline": "^4.3.0", "@fullcalendar/timegrid": "^4.3.0", + "@johmun/vue-tags-input": "^2.1.0", "@playwright/test": "^1.33.0", "@popperjs/core": "^2.11.2", "@types/jquery": "^3.5.16", @@ -62,6 +63,7 @@ "chart.js": "^2.9.4", "chartist": "0.11.4", "ckeditor5-math": "34.1.1", + "colorpare": "^2.2.0", "cropperjs": "1.5.9", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^1.1.5", @@ -135,6 +137,15 @@ "node": ">=16" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -173,35 +184,35 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", - "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", + "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.6", "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", + "@babel/traverse": "^7.22.6", "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.2" }, "engines": { "node": ">=6.9.0" @@ -212,14 +223,14 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.5.tgz", - "integrity": "sha512-C69RWYNYtrgIRE5CmTd77ZiLDXqgBipahJc/jHP3sLcAGj6AJzxNIuKNpVnICqbyK7X3pFUfEvL++rvtbQpZkQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.6.tgz", + "integrity": "sha512-KAom7E7d6bAh5/PflF3luynWlDLOIqfX+ZJcL0LRs6/6rtXJmJxPiWuIGfxNPtcWdtQ5lSSJbKbQlz/c/R60Ng==", "dev": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "eslint-visitor-keys": "^2.1.0" }, "engines": { "node": "^10.13.0 || ^12.13.0 || >=14.0.0" @@ -269,16 +280,16 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", - "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.5", + "@babel/compat-data": "^7.22.6", "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" }, "engines": { "node": ">=6.9.0" @@ -288,9 +299,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz", + "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -300,8 +311,8 @@ "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" + "@babel/helper-split-export-declaration": "^7.22.6", + "@nicolo-ribaudo/semver-v6": "^6.3.3" }, "engines": { "node": ">=6.9.0" @@ -311,14 +322,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz", + "integrity": "sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "regexpu-core": "^5.3.1" }, "engines": { "node": ">=6.9.0" @@ -344,6 +355,15 @@ "@babel/core": "^7.4.0-0" } }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", @@ -502,9 +522,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { "@babel/types": "^7.22.5" @@ -556,13 +576,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", "dev": true, "dependencies": { "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", + "@babel/traverse": "^7.22.6", "@babel/types": "^7.22.5" }, "engines": { @@ -584,9 +604,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz", + "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1098,19 +1118,19 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, "engines": { @@ -1506,9 +1526,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", + "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1618,17 +1638,17 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.5.tgz", - "integrity": "sha512-bg4Wxd1FWeFx3daHFTWk1pkSWK/AyQuiyAoeZAOkAOUBjnZPH6KT7eMxouV47tQ6hl6ax2zyAWBdWZXbrvXlaw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.6.tgz", + "integrity": "sha512-+AGkst7Kqq3QUflKGkhWWMRb9vaKamoreNmYc+sjsIpOp+TsyU0exhp3RlwjQa/HdlKkPt3AMDwfg8Hpt9Vwqg==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", "babel-plugin-polyfill-corejs2": "^0.4.3", "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "semver": "^6.3.0" + "babel-plugin-polyfill-regenerator": "^0.5.0" }, "engines": { "node": ">=6.9.0" @@ -1777,13 +1797,13 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", - "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.6.tgz", + "integrity": "sha512-IHr0AXHGk8oh8HYSs45Mxuv6iySUBwDTIzJSnXN7PURqHdxJVQlCoXmKJgyvSS9bcNf9NVRVE35z+LkCvGmi6w==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.22.6", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", @@ -1814,7 +1834,7 @@ "@babel/plugin-transform-block-scoping": "^7.22.5", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", "@babel/plugin-transform-computed-properties": "^7.22.5", "@babel/plugin-transform-destructuring": "^7.22.5", "@babel/plugin-transform-dotall-regex": "^7.22.5", @@ -1839,7 +1859,7 @@ "@babel/plugin-transform-object-rest-spread": "^7.22.5", "@babel/plugin-transform-object-super": "^7.22.5", "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", "@babel/plugin-transform-parameters": "^7.22.5", "@babel/plugin-transform-private-methods": "^7.22.5", "@babel/plugin-transform-private-property-in-object": "^7.22.5", @@ -1857,11 +1877,11 @@ "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", "@babel/preset-modules": "^0.1.5", "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", "babel-plugin-polyfill-corejs2": "^0.4.3", "babel-plugin-polyfill-corejs3": "^0.8.1", "babel-plugin-polyfill-regenerator": "^0.5.0", - "core-js-compat": "^3.30.2", - "semver": "^6.3.0" + "core-js-compat": "^3.31.0" }, "engines": { "node": ">=6.9.0" @@ -1912,9 +1932,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" @@ -1938,9 +1958,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz", + "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.5", @@ -1948,8 +1968,8 @@ "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.6", "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" @@ -2314,18 +2334,6 @@ "node": ">=8" } }, - "node_modules/@ckeditor/ckeditor5-dev-utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@ckeditor/ckeditor5-dev-utils/node_modules/postcss-loader": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.3.0.tgz", @@ -2368,21 +2376,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/@ckeditor/ckeditor5-dev-utils/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@ckeditor/ckeditor5-dev-utils/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2395,12 +2388,6 @@ "node": ">=8" } }, - "node_modules/@ckeditor/ckeditor5-dev-utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@ckeditor/ckeditor5-dev-webpack-plugin": { "version": "30.5.0", "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-dev-webpack-plugin/-/ckeditor5-dev-webpack-plugin-30.5.0.tgz", @@ -2474,33 +2461,6 @@ "node": ">=8" } }, - "node_modules/@ckeditor/ckeditor5-dev-webpack-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@ckeditor/ckeditor5-dev-webpack-plugin/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@ckeditor/ckeditor5-dev-webpack-plugin/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2513,12 +2473,6 @@ "node": ">=8" } }, - "node_modules/@ckeditor/ckeditor5-dev-webpack-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@ckeditor/ckeditor5-easy-image": { "version": "34.2.0", "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-34.2.0.tgz", @@ -3902,6 +3856,18 @@ "node": ">=8" } }, + "node_modules/@johmun/vue-tags-input": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@johmun/vue-tags-input/-/vue-tags-input-2.1.0.tgz", + "integrity": "sha512-Fdwfss/TqCqMJbGAkmlzKbcG/ia1MstYjhqPBj+zG7h/166tIcE1TIftUxhT9LZ+RWjRSG0EFA1UyaHQSr3k3Q==", + "dev": true, + "dependencies": { + "vue": "^2.6.10" + }, + "peerDependencies": { + "vue": "2.x" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -3935,9 +3901,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -3975,6 +3941,15 @@ "eslint-scope": "5.1.1" } }, + "node_modules/@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4020,39 +3995,6 @@ "semver": "^7.3.5" } }, - "node_modules/@npmcli/fs/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@npmcli/move-file": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", @@ -4112,9 +4054,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.1.0.tgz", - "integrity": "sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -4296,9 +4238,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", - "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==", + "version": "20.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", + "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", "dev": true }, "node_modules/@types/parse-json": { @@ -4366,17 +4308,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", - "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz", + "integrity": "sha512-A5l/eUAug103qtkwccSCxn8ZRwT+7RXWkFECdA4Cvl1dOlDUgTpAOfSEElZn2uSUxhdDpnCdetrf0jvU4qrL+g==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/type-utils": "5.61.0", + "@typescript-eslint/utils": "5.61.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", @@ -4399,48 +4341,15 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", - "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz", + "integrity": "sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/typescript-estree": "5.61.0", "debug": "^4.3.4" }, "engines": { @@ -4460,13 +4369,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", - "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz", + "integrity": "sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/visitor-keys": "5.61.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4477,13 +4386,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", - "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz", + "integrity": "sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "5.61.0", + "@typescript-eslint/utils": "5.61.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -4504,9 +4413,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", - "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.61.0.tgz", + "integrity": "sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4517,13 +4426,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", - "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz", + "integrity": "sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/visitor-keys": "5.61.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4543,51 +4452,18 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", - "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.61.0.tgz", + "integrity": "sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.61.0", + "@typescript-eslint/types": "5.61.0", + "@typescript-eslint/typescript-estree": "5.61.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -4602,46 +4478,13 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", - "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz", + "integrity": "sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.61.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -5059,9 +4902,9 @@ } }, "node_modules/acorn-globals/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -5608,6 +5451,15 @@ "node": ">=8" } }, + "node_modules/babel-loader/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -5653,6 +5505,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", @@ -6078,9 +5939,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001503", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz", - "integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==", + "version": "1.0.30001512", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", + "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==", "dev": true, "funding": [ { @@ -6489,6 +6350,12 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/colorpare": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/colorpare/-/colorpare-2.2.0.tgz", + "integrity": "sha512-GVtxzF1YKkeoKgYa2PBOOS6reC/D1qewhv9se+8Nkh0zfY/JNaIuc2OdORLP8p+4GP7Z9IDYrkjz9XUak6imxQ==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6699,18 +6566,6 @@ "webpack": "^4.27.0 || ^5.0.0" } }, - "node_modules/css-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/css-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -6729,27 +6584,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/css-loader/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/css-minimizer-webpack-plugin": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-1.3.0.tgz", @@ -7597,6 +7431,15 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/css-minimizer-webpack-plugin/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/css-minimizer-webpack-plugin/node_modules/stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -8184,9 +8027,9 @@ } }, "node_modules/dotenv": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.2.0.tgz", - "integrity": "sha512-jcq2vR1DY1+QA+vH58RIrWLDZOifTGmyQJWzP9arDUbgZcySdzuBb1WvhWZzZtiXgfm+GW2pjBqStqlfpzq7wQ==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "dev": true, "engines": { "node": ">=12" @@ -8225,9 +8068,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.432", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.432.tgz", - "integrity": "sha512-yz3U/khQgAFT2HURJA3/F4fKIyO2r5eK09BQzBZFd6BvBSSaRuzKc2ZNBHtJcO75/EKiRYbVYJZ2RB0P4BuD2g==", + "version": "1.4.449", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.449.tgz", + "integrity": "sha512-TxLRpRUj/107ATefeP8VIUWNOv90xJxZZbCW/eIbSZQiuiFANCx2b7u+GbVc9X4gU+xnbvypNMYVM/WArE1DNQ==", "dev": true }, "node_modules/emittery": { @@ -8295,9 +8138,9 @@ } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -8523,9 +8366,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz", - "integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==", + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz", + "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.3.0", @@ -8540,42 +8383,9 @@ "node": "^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-plugin-vue/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -8834,50 +8644,23 @@ "node": ">= 4" } }, - "node_modules/eslint/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint/node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8902,12 +8685,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -9112,9 +8889,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -9255,21 +9032,21 @@ "dev": true }, "node_modules/flow-parser": { - "version": "0.209.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.209.0.tgz", - "integrity": "sha512-uD7Du+9xC/gGnOyk3kANQmtgWWKANWcKGJ84Wu0NSjTaVING3GqUAsywUPAl3fEYKLVVIcDWiaQ8+R6qzghwmA==", + "version": "0.211.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.211.0.tgz", + "integrity": "sha512-Ftqkqisn4MA8u+1I7KGYz35y/RtLsRETsK4qrH6KkDUjxnC4mgq3CcXbckHpGyfTErqMyVhJnlJ56feEn9Cn7A==", "dev": true, "engines": { "node": ">=0.4.0" } }, "node_modules/flow-remove-types": { - "version": "2.209.0", - "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.209.0.tgz", - "integrity": "sha512-p8Tvy95IunOvO0PVSb/rqxUqVXRS+G9aLSkDU56eGNTJ4lEdPbnXd+LCUUb3Ntl5t0L0Llndja6cQqjovC1qaQ==", + "version": "2.211.0", + "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.211.0.tgz", + "integrity": "sha512-R5NA46R8/2UTbRnl2vwcZk1MyASKh60sXUM/ekMBgu/lIgAhMCQo8PMpqNEAe/Wn2Sr0siourTb8dbW/6e9aPA==", "dev": true, "dependencies": { - "flow-parser": "^0.209.0", + "flow-parser": "^0.211.0", "pirates": "^3.0.2", "vlq": "^0.2.1" }, @@ -9624,10 +9401,10 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/growly": { @@ -10510,6 +10287,15 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -10548,6 +10334,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/istanbul-lib-report/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11869,33 +11664,6 @@ "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-snapshot/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11908,12 +11676,6 @@ "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/jest-util": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", @@ -12329,9 +12091,9 @@ } }, "node_modules/jsdom/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -12341,15 +12103,14 @@ } }, "node_modules/jsdom/node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" + "esutils": "^2.0.2" }, "bin": { "escodegen": "bin/escodegen.js", @@ -12700,13 +12461,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", "dev": true, "optional": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -13097,39 +12858,6 @@ "which": "^2.0.2" } }, - "node_modules/node-notifier/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-notifier/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-notifier/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/node-releases": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", @@ -13191,9 +12919,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz", - "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.6.tgz", + "integrity": "sha512-vSZ4miHQ4FojLjmz2+ux4B0/XA16jfwt/LBzIUftDpRd8tujHFkXjMyLwjS08fIZCzesj2z7gJukOKJwqebJAQ==", "dev": true }, "node_modules/object-assign": { @@ -13581,9 +13309,9 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, "engines": { "node": ">= 6" @@ -13884,18 +13612,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/postcss-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/postcss-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -13914,27 +13630,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/postcss-merge-longhand": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", @@ -15148,9 +14843,9 @@ "dev": true }, "node_modules/sanitize-html": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz", - "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==", "dev": true, "dependencies": { "deepmerge": "^4.2.2", @@ -15183,9 +14878,9 @@ } }, "node_modules/sass": { - "version": "1.63.4", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.4.tgz", - "integrity": "sha512-Sx/+weUmK+oiIlI+9sdD0wZHsqpbgQg8wSwSnGBjwb5GwqFhYNwwnI+UWZtLjKvKyFlKkatRK235qQ3mokyPoQ==", + "version": "1.63.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", + "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -15236,18 +14931,6 @@ } } }, - "node_modules/sass-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/sass-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -15266,27 +14949,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/sass-loader/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sass-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -15330,14 +14992,38 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -16041,9 +15727,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz", + "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -16147,6 +15833,15 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/terser-webpack-plugin/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/terser-webpack-plugin/node_modules/webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", @@ -16158,9 +15853,9 @@ } }, "node_modules/terser/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -16292,9 +15987,9 @@ } }, "node_modules/ts-loader": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz", - "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -16362,33 +16057,6 @@ "node": ">=8" } }, - "node_modules/ts-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-loader/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -16401,12 +16069,6 @@ "node": ">=8" } }, - "node_modules/ts-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -16476,9 +16138,9 @@ } }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -16773,9 +16435,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -16813,12 +16475,12 @@ } }, "node_modules/vue-eslint-parser/node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -16844,39 +16506,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/vue-eslint-parser/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/vue-eslint-parser/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/vue-gettext": { "version": "2.1.12", "resolved": "https://registry.npmjs.org/vue-gettext/-/vue-gettext-2.1.12.tgz", @@ -17133,9 +16762,9 @@ } }, "node_modules/webpack": { - "version": "5.87.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.87.0.tgz", - "integrity": "sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==", + "version": "5.88.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -17305,9 +16934,9 @@ } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" diff --git a/package.json b/package.json index 1e7acb6..d0d17fc 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@fullcalendar/resource-timeline": "^4.3.0", "@fullcalendar/timegrid": "^4.3.0", "@playwright/test": "^1.33.0", + "@johmun/vue-tags-input": "^2.1.0", "@popperjs/core": "^2.11.2", "@types/jquery": "^3.5.16", "@types/jqueryui": "^1.12.16", @@ -72,6 +73,7 @@ "chart.js": "^2.9.4", "chartist": "0.11.4", "ckeditor5-math": "34.1.1", + "colorpare": "^2.2.0", "cropperjs": "1.5.9", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^1.1.5", diff --git a/public/assets/images/checkered-background.png b/public/assets/images/checkered-background.png new file mode 100644 index 0000000..9f5a292 Binary files /dev/null and b/public/assets/images/checkered-background.png differ diff --git a/public/pictures/stock-images/.gitkeep b/public/pictures/stock-images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resources/assets/javascripts/bootstrap/stock-images.js b/resources/assets/javascripts/bootstrap/stock-images.js new file mode 100644 index 0000000..6ed6edf --- /dev/null +++ b/resources/assets/javascripts/bootstrap/stock-images.js @@ -0,0 +1,5 @@ +import StockImages from '../lib/stock-images.js'; + +STUDIP.domReady(() => { + StockImages.init(); +}); diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js index d40d4e0..915d960 100644 --- a/resources/assets/javascripts/entry-base.js +++ b/resources/assets/javascripts/entry-base.js @@ -84,6 +84,7 @@ import "./bootstrap/oer.js" import "./bootstrap/courseware.js" import "./bootstrap/responsive-navigation.js" import "./bootstrap/treeview.js" +import "./bootstrap/stock-images.js" import "./mvv_course_wizard.js" import "./mvv.js" diff --git a/resources/assets/javascripts/lib/stock-images.js b/resources/assets/javascripts/lib/stock-images.js new file mode 100644 index 0000000..330fbc9 --- /dev/null +++ b/resources/assets/javascripts/lib/stock-images.js @@ -0,0 +1,23 @@ +const StockImages = { + init() { + const stockImagesPage = document.querySelector('div.stock-images'); + if (stockImagesPage !== null) { + Promise.all([window.STUDIP.Vue.load(), StockImages.plugin()]).then( + ([{ Vue, createApp, store }, StockImagesPlugin]) => { + Vue.use(StockImagesPlugin, { store }); + createApp({ + el: stockImagesPage, + render: (h) => { + return h(Vue.component('StockImagesPage'), { props: {} }); + }, + }); + } + ); + } + }, + plugin() { + return import('@/vue/plugins/stock-images.js').then(({ StockImagesPlugin }) => StockImagesPlugin); + }, +}; + +export default StockImages; diff --git a/resources/vue/components/ActiveFilter.vue b/resources/vue/components/ActiveFilter.vue new file mode 100644 index 0000000..d4654fa --- /dev/null +++ b/resources/vue/components/ActiveFilter.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/resources/vue/components/SearchWithFilter.vue b/resources/vue/components/SearchWithFilter.vue new file mode 100644 index 0000000..1336e96 --- /dev/null +++ b/resources/vue/components/SearchWithFilter.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/resources/vue/components/courseware/CoursewareFileChooser.vue b/resources/vue/components/courseware/CoursewareFileChooser.vue index 0200684..6571b34 100644 --- a/resources/vue/components/courseware/CoursewareFileChooser.vue +++ b/resources/vue/components/courseware/CoursewareFileChooser.vue @@ -128,6 +128,12 @@ export default { this.getFolderFiles(); } }, + value(newValue, oldValue) { + if (newValue === '') { + this.selectedFolderId = ''; + this.currentValue = ''; + } + }, }, }; diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue index abce90b..c993300 100644 --- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue +++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue @@ -103,7 +103,7 @@ v-model="currentTextColor" >