diff --git a/assets/controllers/common/markdown_controller.js b/assets/controllers/common/markdown_controller.js index b6ef0034..c6cb97df 100644 --- a/assets/controllers/common/markdown_controller.js +++ b/assets/controllers/common/markdown_controller.js @@ -56,12 +56,16 @@ export default class MarkdownController extends Controller { this.element.innerHTML = DOMPurify.sanitize(MarkdownController._marked.parse(this.unescapeHTML(raw))); for(let a of this.element.querySelectorAll('a')) { - //Mark all links as external - a.classList.add('link-external'); - //Open links in new tag - a.setAttribute('target', '_blank'); - //Dont track - a.setAttribute('rel', 'noopener'); + // test if link is absolute + var r = new RegExp('^(?:[a-z+]+:)?//', 'i'); + if (r.test(a.getAttribute('href'))) { + //Mark all links as external + a.classList.add('link-external'); + //Open links in new tag + a.setAttribute('target', '_blank'); + //Dont track + a.setAttribute('rel', 'noopener'); + } } //Apply bootstrap styles to tables @@ -108,4 +112,4 @@ export default class MarkdownController extends Controller { gfm: true, }); }*/ -} \ No newline at end of file +} diff --git a/assets/controllers/elements/attachment_autocomplete_controller.js b/assets/controllers/elements/attachment_autocomplete_controller.js index f8bc301e..0175b284 100644 --- a/assets/controllers/elements/attachment_autocomplete_controller.js +++ b/assets/controllers/elements/attachment_autocomplete_controller.js @@ -42,6 +42,7 @@ export default class extends Controller { selectOnTab: true, //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', + dropdownParent: 'body', render: { item: (data, escape) => { return '' + escape(data.label) + ''; diff --git a/assets/controllers/elements/part_select_controller.js b/assets/controllers/elements/part_select_controller.js index 5abd5ba3..0658f4b4 100644 --- a/assets/controllers/elements/part_select_controller.js +++ b/assets/controllers/elements/part_select_controller.js @@ -16,6 +16,7 @@ export default class extends Controller { searchField: ["name", "description", "category", "footprint"], valueField: "id", labelField: "name", + dropdownParent: 'body', preload: "focus", render: { item: (data, escape) => { @@ -71,4 +72,4 @@ export default class extends Controller { //Destroy the TomSelect instance this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/select_controller.js b/assets/controllers/elements/select_controller.js index cdafe4d0..f933731a 100644 --- a/assets/controllers/elements/select_controller.js +++ b/assets/controllers/elements/select_controller.js @@ -44,6 +44,7 @@ export default class extends Controller { allowEmptyOption: true, selectOnTab: true, maxOptions: null, + dropdownParent: 'body', render: { item: this.renderItem.bind(this), @@ -108,4 +109,4 @@ export default class extends Controller { //Destroy the TomSelect instance this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/select_multiple_controller.js b/assets/controllers/elements/select_multiple_controller.js index df37871d..daa6b0a1 100644 --- a/assets/controllers/elements/select_multiple_controller.js +++ b/assets/controllers/elements/select_multiple_controller.js @@ -29,6 +29,7 @@ export default class extends Controller { this._tomSelect = new TomSelect(this.element, { maxItems: 1000, allowEmptyOption: true, + dropdownParent: 'body', plugins: ['remove_button'], }); } @@ -39,4 +40,4 @@ export default class extends Controller { this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/controllers/elements/static_file_autocomplete_controller.js b/assets/controllers/elements/static_file_autocomplete_controller.js index 31ca0314..0421a26d 100644 --- a/assets/controllers/elements/static_file_autocomplete_controller.js +++ b/assets/controllers/elements/static_file_autocomplete_controller.js @@ -50,6 +50,7 @@ export default class extends Controller { valueField: 'text', searchField: 'text', orderField: 'text', + dropdownParent: 'body', //This a an ugly solution to disable the delimiter parsing of the TomSelect plugin delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING', diff --git a/assets/controllers/elements/structural_entity_select_controller.js b/assets/controllers/elements/structural_entity_select_controller.js index a1114a97..5c6f9490 100644 --- a/assets/controllers/elements/structural_entity_select_controller.js +++ b/assets/controllers/elements/structural_entity_select_controller.js @@ -54,6 +54,7 @@ export default class extends Controller { maxItems: 1, delimiter: "$$VERY_LONG_DELIMITER_THAT_SHOULD_NEVER_APPEAR$$", splitOn: null, + dropdownParent: 'body', searchField: [ {field: "text", weight : 2}, diff --git a/assets/controllers/elements/tagsinput_controller.js b/assets/controllers/elements/tagsinput_controller.js index 1f10c457..53bf7608 100644 --- a/assets/controllers/elements/tagsinput_controller.js +++ b/assets/controllers/elements/tagsinput_controller.js @@ -43,6 +43,7 @@ export default class extends Controller { selectOnTab: true, createOnBlur: true, create: true, + dropdownParent: 'body', }; if(this.element.dataset.autocomplete) { @@ -73,4 +74,4 @@ export default class extends Controller { //Destroy the TomSelect instance this._tomSelect.destroy(); } -} \ No newline at end of file +} diff --git a/assets/css/components/ckeditor.css b/assets/css/components/ckeditor.css index d6b3def4..5f093bf2 100644 --- a/assets/css/components/ckeditor.css +++ b/assets/css/components/ckeditor.css @@ -71,6 +71,8 @@ --ck-color-button-on-hover-background: var(--bs-secondary-bg); --ck-color-button-on-active-background: var(--bs-secondary-bg); --ck-color-button-on-disabled-background: var(--bs-secondary-bg); - --ck-color-button-on-color: var(--bs-primary) + --ck-color-button-on-color: var(--bs-primary); -} \ No newline at end of file + --ck-content-font-color: var(--ck-color-base-text); + +} diff --git a/composer.json b/composer.json index 8e3d1194..80b413f8 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,7 @@ "doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/orm": "^3.2.0", "dompdf/dompdf": "^v3.0.0", - "florianv/swap": "^4.0", - "florianv/swap-bundle": "dev-master", + "part-db/swap-bundle": "^6.0.0", "gregwar/captcha-bundle": "^2.1.0", "hshn/base64-encoded-file": "^5.0", "jbtronics/2fa-webauthn": "^3.0.0", diff --git a/composer.lock b/composer.lock index 6b9888d7..6de15830 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": "09b78f345ea8115b5b29ea3e67dcb579", + "content-hash": "fe6dfc229f551945cfa6be8ca26a437e", "packages": [ { "name": "amphp/amp", @@ -968,7 +968,7 @@ }, { "name": "api-platform/doctrine-common", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-common.git", @@ -1050,13 +1050,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-common/tree/v4.1.22" + "source": "https://github.com/api-platform/doctrine-common/tree/v4.1.23" }, "time": "2025-08-18T13:30:43+00:00" }, { "name": "api-platform/doctrine-orm", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/doctrine-orm.git", @@ -1135,13 +1135,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/doctrine-orm/tree/v4.1.22" + "source": "https://github.com/api-platform/doctrine-orm/tree/v4.1.23" }, "time": "2025-06-06T14:56:47+00:00" }, { "name": "api-platform/documentation", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/documentation.git", @@ -1203,7 +1203,7 @@ }, { "name": "api-platform/http-cache", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/http-cache.git", @@ -1275,13 +1275,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/http-cache/tree/v4.1.22" + "source": "https://github.com/api-platform/http-cache/tree/v4.1.23" }, "time": "2025-06-06T14:56:47+00:00" }, { "name": "api-platform/hydra", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/hydra.git", @@ -1360,13 +1360,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/hydra/tree/v4.1.22" + "source": "https://github.com/api-platform/hydra/tree/v4.1.23" }, "time": "2025-07-15T14:10:59+00:00" }, { "name": "api-platform/json-api", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/json-api.git", @@ -1439,13 +1439,13 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/json-api/tree/v4.1.22" + "source": "https://github.com/api-platform/json-api/tree/v4.1.23" }, "time": "2025-08-06T07:56:58+00:00" }, { "name": "api-platform/json-schema", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/json-schema.git", @@ -1518,13 +1518,13 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/json-schema/tree/v4.1.22" + "source": "https://github.com/api-platform/json-schema/tree/v4.1.23" }, "time": "2025-06-29T12:24:14+00:00" }, { "name": "api-platform/jsonld", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/jsonld.git", @@ -1596,22 +1596,22 @@ "rest" ], "support": { - "source": "https://github.com/api-platform/jsonld/tree/v4.1.22" + "source": "https://github.com/api-platform/jsonld/tree/v4.1.23" }, "time": "2025-07-25T10:05:30+00:00" }, { "name": "api-platform/metadata", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/metadata.git", - "reference": "782477dd28cc675909597bfa47af7c1f85659ffa" + "reference": "58b25f9a82c12727afab09b5a311828aacff8e88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/metadata/zipball/782477dd28cc675909597bfa47af7c1f85659ffa", - "reference": "782477dd28cc675909597bfa47af7c1f85659ffa", + "url": "https://api.github.com/repos/api-platform/metadata/zipball/58b25f9a82c12727afab09b5a311828aacff8e88", + "reference": "58b25f9a82c12727afab09b5a311828aacff8e88", "shasum": "" }, "require": { @@ -1693,13 +1693,13 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/metadata/tree/v4.1.22" + "source": "https://github.com/api-platform/metadata/tree/v4.1.23" }, - "time": "2025-08-29T15:13:26+00:00" + "time": "2025-09-05T09:06:52+00:00" }, { "name": "api-platform/openapi", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/openapi.git", @@ -1780,13 +1780,13 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/openapi/tree/v4.1.22" + "source": "https://github.com/api-platform/openapi/tree/v4.1.23" }, "time": "2025-07-29T08:53:27+00:00" }, { "name": "api-platform/serializer", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/serializer.git", @@ -1871,13 +1871,13 @@ "serializer" ], "support": { - "source": "https://github.com/api-platform/serializer/tree/v4.1.22" + "source": "https://github.com/api-platform/serializer/tree/v4.1.23" }, "time": "2025-08-29T15:13:26+00:00" }, { "name": "api-platform/state", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/state.git", @@ -1963,22 +1963,22 @@ "swagger" ], "support": { - "source": "https://github.com/api-platform/state/tree/v4.1.22" + "source": "https://github.com/api-platform/state/tree/v4.1.23" }, "time": "2025-07-16T14:01:52+00:00" }, { "name": "api-platform/symfony", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/symfony.git", - "reference": "735b9a80f3b7a5f528b663cd28489fbe52f52416" + "reference": "e35839489b4e76ffc5fc2b0cbadbbaece75b9ad1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/symfony/zipball/735b9a80f3b7a5f528b663cd28489fbe52f52416", - "reference": "735b9a80f3b7a5f528b663cd28489fbe52f52416", + "url": "https://api.github.com/repos/api-platform/symfony/zipball/e35839489b4e76ffc5fc2b0cbadbbaece75b9ad1", + "reference": "e35839489b4e76ffc5fc2b0cbadbbaece75b9ad1", "shasum": "" }, "require": { @@ -2087,13 +2087,13 @@ "symfony" ], "support": { - "source": "https://github.com/api-platform/symfony/tree/v4.1.22" + "source": "https://github.com/api-platform/symfony/tree/v4.1.23" }, - "time": "2025-07-16T14:01:52+00:00" + "time": "2025-09-05T07:30:37+00:00" }, { "name": "api-platform/validator", - "version": "v4.1.22", + "version": "v4.1.23", "source": { "type": "git", "url": "https://github.com/api-platform/validator.git", @@ -2162,7 +2162,7 @@ "validator" ], "support": { - "source": "https://github.com/api-platform/validator/tree/v4.1.22" + "source": "https://github.com/api-platform/validator/tree/v4.1.23" }, "time": "2025-07-16T14:01:52+00:00" }, @@ -2881,16 +2881,16 @@ }, { "name": "doctrine/dbal", - "version": "4.3.2", + "version": "4.3.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "7669f131d43b880de168b2d2df9687d152d6c762" + "reference": "231959669bb2173194c95636eae7f1b41b2a8b19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/7669f131d43b880de168b2d2df9687d152d6c762", - "reference": "7669f131d43b880de168b2d2df9687d152d6c762", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/231959669bb2173194c95636eae7f1b41b2a8b19", + "reference": "231959669bb2173194c95636eae7f1b41b2a8b19", "shasum": "" }, "require": { @@ -2900,10 +2900,10 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "13.0.0", + "doctrine/coding-standard": "13.0.1", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "2.1.17", + "phpstan/phpstan": "2.1.22", "phpstan/phpstan-phpunit": "2.0.6", "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "11.5.23", @@ -2967,7 +2967,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.3.2" + "source": "https://github.com/doctrine/dbal/tree/4.3.3" }, "funding": [ { @@ -2983,7 +2983,7 @@ "type": "tidelift" } ], - "time": "2025-08-05T13:30:38+00:00" + "time": "2025-09-04T23:52:42+00:00" }, { "name": "doctrine/deprecations", @@ -3035,16 +3035,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.15.1", + "version": "2.16.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "5a305c5e776f9d3eb87f5b94d40d50aff439211d" + "reference": "152d5083f0cd205a278131dc4351a8c94d007fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/5a305c5e776f9d3eb87f5b94d40d50aff439211d", - "reference": "5a305c5e776f9d3eb87f5b94d40d50aff439211d", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/152d5083f0cd205a278131dc4351a8c94d007fe1", + "reference": "152d5083f0cd205a278131dc4351a8c94d007fe1", "shasum": "" }, "require": { @@ -3081,6 +3081,7 @@ "phpunit/phpunit": "^9.6.22", "psr/log": "^1.1.4 || ^2.0 || ^3.0", "symfony/doctrine-messenger": "^6.4 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.0", "symfony/messenger": "^6.4 || ^7.0", "symfony/phpunit-bridge": "^7.2", "symfony/property-info": "^6.4 || ^7.0", @@ -3137,7 +3138,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.15.1" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.16.1" }, "funding": [ { @@ -3153,7 +3154,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T15:48:28+00:00" + "time": "2025-09-05T15:24:53+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -4133,16 +4134,16 @@ }, { "name": "ergebnis/classy", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/ergebnis/classy.git", - "reference": "e5a695e44b083d4a4b4f2a40427301cd2916699d" + "reference": "05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ergebnis/classy/zipball/e5a695e44b083d4a4b4f2a40427301cd2916699d", - "reference": "e5a695e44b083d4a4b4f2a40427301cd2916699d", + "url": "https://api.github.com/repos/ergebnis/classy/zipball/05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef", + "reference": "05c3ac7d8d9d337c4cf1d5602a339f57cb2a27ef", "shasum": "" }, "require": { @@ -4150,11 +4151,11 @@ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "require-dev": { - "ergebnis/composer-normalize": "^2.47.0", - "ergebnis/license": "^2.6.0", - "ergebnis/php-cs-fixer-config": "^6.51.0", - "ergebnis/phpstan-rules": "^2.10.5", - "ergebnis/phpunit-slow-test-detector": "^2.19.1", + "ergebnis/composer-normalize": "^2.48.1", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.54.0", + "ergebnis/phpstan-rules": "^2.11.0", + "ergebnis/phpunit-slow-test-detector": "^2.20.0", "fakerphp/faker": "^1.24.1", "infection/infection": "~0.26.6", "phpstan/extension-installer": "^1.4.3", @@ -4163,7 +4164,7 @@ "phpstan/phpstan-phpunit": "^2.0.7", "phpstan/phpstan-strict-rules": "^2.0.6", "phpunit/phpunit": "^9.6.19", - "rector/rector": "^2.1.2" + "rector/rector": "^2.1.4" }, "type": "library", "autoload": { @@ -4196,208 +4197,7 @@ "issues": "https://github.com/ergebnis/classy/issues", "source": "https://github.com/ergebnis/classy" }, - "time": "2025-08-19T06:14:25+00:00" - }, - { - "name": "florianv/exchanger", - "version": "2.8.1", - "source": { - "type": "git", - "url": "https://github.com/florianv/exchanger.git", - "reference": "9214f51665fb907e7aa2397e21a90c456eb0c448" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/exchanger/zipball/9214f51665fb907e7aa2397e21a90c456eb0c448", - "reference": "9214f51665fb907e7aa2397e21a90c456eb0c448", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-libxml": "*", - "ext-simplexml": "*", - "php": "^7.1.3 || ^8.0", - "php-http/client-implementation": "^1.0", - "php-http/discovery": "^1.6", - "php-http/httplug": "^1.0 || ^2.0", - "psr/http-factory": "^1.0.2", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" - }, - "require-dev": { - "nyholm/psr7": "^1.0", - "php-http/message": "^1.7", - "php-http/mock-client": "^1.0", - "phpunit/phpunit": "^7 || ^8 || ^9.4" - }, - "suggest": { - "php-http/guzzle6-adapter": "Required to use Guzzle for sending HTTP requests", - "php-http/message": "Required to use Guzzle for sending HTTP requests" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Exchanger\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "https://voutzinos.com" - } - ], - "description": "Currency exchange rates framework for PHP", - "homepage": "https://github.com/florianv/exchanger", - "keywords": [ - "Rate", - "conversion", - "currency", - "exchange rates", - "money" - ], - "support": { - "issues": "https://github.com/florianv/exchanger/issues", - "source": "https://github.com/florianv/exchanger/tree/2.8.1" - }, - "time": "2023-11-03T17:11:52+00:00" - }, - { - "name": "florianv/swap", - "version": "4.3.0", - "source": { - "type": "git", - "url": "https://github.com/florianv/swap.git", - "reference": "88edd27fcb95bdc58bbbf9e4b00539a2843d97fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/swap/zipball/88edd27fcb95bdc58bbbf9e4b00539a2843d97fd", - "reference": "88edd27fcb95bdc58bbbf9e4b00539a2843d97fd", - "shasum": "" - }, - "require": { - "florianv/exchanger": "^2.0", - "php": "^7.1.3 || ^8.0" - }, - "require-dev": { - "nyholm/psr7": "^1.0", - "php-http/message": "^1.7", - "php-http/mock-client": "^1.0", - "phpunit/phpunit": "^7 || ^8 || ^9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "psr-4": { - "Swap\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "https://voutzinos.com" - } - ], - "description": "Exchange rates library for PHP", - "keywords": [ - "Rate", - "conversion", - "currency", - "exchange rates", - "money" - ], - "support": { - "issues": "https://github.com/florianv/swap/issues", - "source": "https://github.com/florianv/swap/tree/4.3.0" - }, - "time": "2020-12-28T10:14:12+00:00" - }, - { - "name": "florianv/swap-bundle", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/florianv/symfony-swap.git", - "reference": "c8cd268ad6e2f636f10b91df9850e3941d7f5807" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/florianv/symfony-swap/zipball/c8cd268ad6e2f636f10b91df9850e3941d7f5807", - "reference": "c8cd268ad6e2f636f10b91df9850e3941d7f5807", - "shasum": "" - }, - "require": { - "florianv/swap": "^4.0", - "php": "^7.1.3|^8.0", - "symfony/framework-bundle": "~3.0|~4.0|~5.0|~6.0|~7.0" - }, - "require-dev": { - "nyholm/psr7": "^1.1", - "php-http/guzzle6-adapter": "^1.0", - "php-http/message": "^1.7", - "phpunit/phpunit": "~5.7|~6.0|~7.0|~8.0|~9.0", - "symfony/cache": "~3.0|~4.0|~5.0|~6.0|~7.0" - }, - "suggest": { - "symfony/cache": "For caching" - }, - "default-branch": true, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "Florianv\\SwapBundle\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florian Voutzinos", - "email": "florian@voutzinos.com", - "homepage": "http://florian.voutzinos.com" - } - ], - "description": "Integrates the Swap library with Symfony", - "homepage": "https://github.com/florianv/FlorianvSwapBundle", - "keywords": [ - "Rate", - "bundle", - "conversion", - "currency", - "exchange", - "money", - "symfony" - ], - "support": { - "issues": "https://github.com/florianv/symfony-swap/issues", - "source": "https://github.com/florianv/symfony-swap/tree/master" - }, - "time": "2024-07-09T13:51:01+00:00" + "time": "2025-09-04T10:17:22+00:00" }, { "name": "gregwar/captcha", @@ -6357,22 +6157,23 @@ }, { "name": "liip/imagine-bundle", - "version": "2.13.3", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/liip/LiipImagineBundle.git", - "reference": "3faccde327f91368e51d05ecad49a9cd915abd81" + "reference": "f80dc13e9a454682b8c2255b3487829d2f8a7fe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/3faccde327f91368e51d05ecad49a9cd915abd81", - "reference": "3faccde327f91368e51d05ecad49a9cd915abd81", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/f80dc13e9a454682b8c2255b3487829d2f8a7fe4", + "reference": "f80dc13e9a454682b8c2255b3487829d2f8a7fe4", "shasum": "" }, "require": { "ext-mbstring": "*", "imagine/imagine": "^1.3.2", "php": "^7.2|^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3", "symfony/filesystem": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/finder": "^3.4|^4.4|^5.3|^6.0|^7.0", "symfony/framework-bundle": "^3.4.23|^4.4|^5.3|^6.0|^7.0", @@ -6457,9 +6258,9 @@ ], "support": { "issues": "https://github.com/liip/LiipImagineBundle/issues", - "source": "https://github.com/liip/LiipImagineBundle/tree/2.13.3" + "source": "https://github.com/liip/LiipImagineBundle/tree/2.14.0" }, - "time": "2024-12-12T09:38:23+00:00" + "time": "2025-09-03T06:33:10+00:00" }, { "name": "lorenzo/pinky", @@ -7633,6 +7434,83 @@ }, "time": "2024-04-22T22:05:04+00:00" }, + { + "name": "part-db/exchanger", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/exchanger.git", + "reference": "a43fe79a082e331ec2b24f3579e4fba153743757" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/exchanger/zipball/a43fe79a082e331ec2b24f3579e4fba153743757", + "reference": "a43fe79a082e331ec2b24f3579e4fba153743757", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "php": "^7.1.3 || ^8.0", + "php-http/client-implementation": "^1.0", + "php-http/discovery": "^1.6", + "php-http/httplug": "^1.0 || ^2.0", + "psr/http-factory": "^1.0.2", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/message": "^1.7", + "php-http/message-factory": "^1.1", + "php-http/mock-client": "^1.0", + "phpunit/phpunit": "^7 || ^8 || ^9.4 || ^10.5", + "symfony/http-client": "^5.4 || ^6.4 || ^7.0" + }, + "suggest": { + "php-http/guzzle6-adapter": "Required to use Guzzle for sending HTTP requests", + "php-http/message": "Required to use Guzzle for sending HTTP requests" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Exchanger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "https://voutzinos.com" + }, + { + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "Fork of florianv/exchanger, a library to convert currencies using different exchange rate providers. Modernized to be compatible with Part-DB.", + "homepage": "https://github.com/Part-DB/exchanger", + "keywords": [ + "Rate", + "conversion", + "currency", + "exchange rates", + "money" + ], + "support": { + "source": "https://github.com/Part-DB/exchanger/tree/v3.1.0" + }, + "time": "2025-09-05T19:48:23+00:00" + }, { "name": "part-db/label-fonts", "version": "v1.1.0", @@ -7671,6 +7549,149 @@ }, "time": "2024-02-08T21:44:38+00:00" }, + { + "name": "part-db/swap", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/swap.git", + "reference": "4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/swap/zipball/4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe", + "reference": "4fa57dec2eb1cbe0f6b8c92a2c250ecbe80688fe", + "shasum": "" + }, + "require": { + "part-db/exchanger": "^3.0", + "php": "^7.1.3 || ^8.0", + "php-http/message-factory": "^1.1" + }, + "require-dev": { + "nyholm/psr7": "^1.0", + "php-http/discovery": "^1.0", + "php-http/message": "^1.7", + "php-http/mock-client": "^1.0", + "phpunit/phpunit": "^7 || ^8 || ^9", + "symfony/http-client": "^5.4||^6.0||^7.0" + }, + "suggest": { + "php-http/discovery": "If you are not using `useHttpClient` but instead want to auto-discover HttpClient" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Swap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "https://voutzinos.com" + }, + { + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "Fork of florianv/swap modernized for use in Part-DB. Exchange rates library for PHP", + "keywords": [ + "Rate", + "conversion", + "currency", + "exchange rates", + "money" + ], + "support": { + "source": "https://github.com/Part-DB/swap/tree/v5.0.0" + }, + "time": "2025-09-05T17:10:01+00:00" + }, + { + "name": "part-db/swap-bundle", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/Part-DB/symfony-swap.git", + "reference": "fd78ebfbd762b1d76b4d71f713f39add63dec62b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Part-DB/symfony-swap/zipball/fd78ebfbd762b1d76b4d71f713f39add63dec62b", + "reference": "fd78ebfbd762b1d76b4d71f713f39add63dec62b", + "shasum": "" + }, + "require": { + "part-db/exchanger": "^3.1.0", + "part-db/swap": "^5.0", + "php": "^7.1.3|^8.0", + "psr/http-client": "^1.0", + "symfony/framework-bundle": "~3.0|~4.0|~5.0|~6.0|~7.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/guzzle6-adapter": "^1.0", + "php-http/message": "^1.7", + "phpunit/phpunit": "~5.7|~6.0|~7.0|~8.0|~9.0", + "symfony/cache": "~3.0|~4.0|~5.0|~6.0|~7.0", + "symfony/http-client": "~7.0|~6.0|~5.0" + }, + "suggest": { + "symfony/cache": "For caching" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Florianv\\SwapBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florian Voutzinos", + "email": "florian@voutzinos.com", + "homepage": "http://florian.voutzinos.com" + }, + { + "name": "Jan Böhmer", + "email": "mail@jan-boehmer.de" + } + ], + "description": "Fork of florianv/swap-bundle, modernized for use with Part-DB. Integrates the Swap library with Symfony", + "homepage": "https://github.com/florianv/FlorianvSwapBundle", + "keywords": [ + "Rate", + "bundle", + "conversion", + "currency", + "exchange", + "money", + "symfony" + ], + "support": { + "source": "https://github.com/Part-DB/symfony-swap/tree/v6.1.0" + }, + "time": "2025-09-05T19:52:56+00:00" + }, { "name": "php-http/discovery", "version": "1.20.0", @@ -7807,6 +7828,61 @@ }, "time": "2024-09-23T11:39:58+00:00" }, + { + "name": "php-http/message-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/1.1.0" + }, + "abandoned": "psr/http-factory", + "time": "2023-04-14T14:16:17+00:00" + }, { "name": "php-http/promise", "version": "1.3.1", @@ -18333,16 +18409,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.35", + "version": "11.5.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91" + "reference": "264a87c7ef68b1ab9af7172357740dc266df5957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d341ee94ee5007b286fc7907b383aae6b5b3cc91", - "reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/264a87c7ef68b1ab9af7172357740dc266df5957", + "reference": "264a87c7ef68b1ab9af7172357740dc266df5957", "shasum": "" }, "require": { @@ -18414,7 +18490,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.35" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.36" }, "funding": [ { @@ -18438,20 +18514,20 @@ "type": "tidelift" } ], - "time": "2025-08-28T05:13:54+00:00" + "time": "2025-09-03T06:24:17+00:00" }, { "name": "rector/rector", - "version": "2.1.4", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "fe613c528819222f8686a9a037a315ef9d4915b3" + "reference": "729aabc0ec66e700ef164e26454a1357f222a2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/fe613c528819222f8686a9a037a315ef9d4915b3", - "reference": "fe613c528819222f8686a9a037a315ef9d4915b3", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/729aabc0ec66e700ef164e26454a1357f222a2f3", + "reference": "729aabc0ec66e700ef164e26454a1357f222a2f3", "shasum": "" }, "require": { @@ -18490,7 +18566,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.1.4" + "source": "https://github.com/rectorphp/rector/tree/2.1.6" }, "funding": [ { @@ -18498,7 +18574,7 @@ "type": "github" } ], - "time": "2025-08-15T14:41:36+00:00" + "time": "2025-09-05T15:43:08+00:00" }, { "name": "roave/security-advisories", @@ -18506,12 +18582,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "e7589e01dc8452bfecb4c8df977346cd3132650f" + "reference": "dc5c4ede5c331ae21fb68947ff89672df9b7cc7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e7589e01dc8452bfecb4c8df977346cd3132650f", - "reference": "e7589e01dc8452bfecb4c8df977346cd3132650f", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/dc5c4ede5c331ae21fb68947ff89672df9b7cc7d", + "reference": "dc5c4ede5c331ae21fb68947ff89672df9b7cc7d", "shasum": "" }, "conflict": { @@ -18935,7 +19011,7 @@ "marshmallow/nova-tiptap": "<5.7", "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", - "mautic/core": "<5.2.6|>=6.0.0.0-alpha,<6.0.2", + "mautic/core": "<5.2.8|>=6.0.0.0-alpha,<6.0.5", "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", "maximebf/debugbar": "<1.19", "mdanter/ecc": "<2", @@ -19081,7 +19157,7 @@ "pixelfed/pixelfed": "<0.12.5", "plotly/plotly.js": "<2.25.2", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<5.25.2", + "pocketmine/pocketmine-mp": "<5.32.1", "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", @@ -19089,7 +19165,7 @@ "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.1.6", + "prestashop/prestashop": "<8.2.3", "prestashop/productcomments": "<5.0.2", "prestashop/ps_contactinfo": "<=3.3.2", "prestashop/ps_emailsubscription": "<2.6.1", @@ -19463,7 +19539,7 @@ "type": "tidelift" } ], - "time": "2025-08-29T15:04:47+00:00" + "time": "2025-09-04T20:05:35+00:00" }, { "name": "sebastian/cli-parser", @@ -20959,7 +21035,6 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "florianv/swap-bundle": 20, "roave/security-advisories": 20 }, "prefer-stable": false, diff --git a/config/packages/nelmio_security.yaml b/config/packages/nelmio_security.yaml index 1cb74da7..c283cd8e 100644 --- a/config/packages/nelmio_security.yaml +++ b/config/packages/nelmio_security.yaml @@ -69,9 +69,3 @@ nelmio_security: - 'data:' block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport # upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport - -when@dev: - # disables the Content-Security-Policy header - nelmio_security: - csp: - enabled: false \ No newline at end of file diff --git a/config/packages/swap.yaml b/config/packages/swap.yaml index beb41d26..4ef8fbdf 100644 --- a/config/packages/swap.yaml +++ b/config/packages/swap.yaml @@ -5,6 +5,12 @@ florianv_swap: providers: european_central_bank: ~ # European Central Bank (only works for EUR base currency) - fixer: # Fixer.io (needs an API key) - access_key: "%env(string:default:settings:exchange_rate:fixerApiKey:INVALID)%" - #exchange_rates_api: ~ \ No newline at end of file + central_bank_of_czech_republic: ~ + central_bank_of_republic_turkey: ~ + national_bank_of_romania: ~ + + fixer: # Fixer.io (needs an API key) + access_key: "%env(string:settings:exchange_rate:fixerApiKey)%" + + frankfurter: ~ + fawazahmed_currency_api: ~ diff --git a/config/permissions.yaml b/config/permissions.yaml index e5a1d65b..8cbd60c3 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -359,6 +359,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co label: "perm.revert_elements" alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] apiTokenRole: ROLE_API_EDIT + import: + label: "perm.import" + alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles' ] + apiTokenRole: ROLE_API_EDIT api: label: "perm.api" diff --git a/docs/installation/installation_guide-debian.md b/docs/installation/installation_guide-debian.md index 312fe21e..b3c61126 100644 --- a/docs/installation/installation_guide-debian.md +++ b/docs/installation/installation_guide-debian.md @@ -28,9 +28,14 @@ It is recommended to install Part-DB on a 64-bit system, as the 32-bit version o For the installation of Part-DB, we need some prerequisites. They can be installed by running the following command: ```bash -sudo apt install git curl zip ca-certificates software-properties-common apt-transport-https lsb-release nano wget +sudo apt update && apt upgrade +sudo apt install git curl zip ca-certificates software-properties-common \ + apt-transport-https lsb-release nano wget sqlite3 ``` +Please run `sqlite3 --version` to assert that the SQLite version is 3.35 or higher. +Otherwise some database migrations will not succeed. + ### Install PHP and apache2 Part-DB is written in [PHP](https://php.net) and therefore needs a PHP interpreter to run. Part-DB needs PHP 8.2 or diff --git a/docs/usage/bom_import.md b/docs/usage/bom_import.md index 94a06d55..b4bcb2be 100644 --- a/docs/usage/bom_import.md +++ b/docs/usage/bom_import.md @@ -34,3 +34,12 @@ select the BOM file you want to import and some options for the import process: has a different format and does not work with this type. You can generate this BOM file by going to "File" -> "Fabrication Outputs" -> "Bill of Materials" in Pcbnew and save the file to your desired location. +* **KiCAD Schematic BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated + by [KiCAD Eeschema](https://www.kicad.org/). + You can generate this BOM file by going to "Tools" -> "Generate Bill of Materials" in Eeschema and save the file to your + desired location. In the next step you can customize the mapping of the fields in Part-DB, if you have any special fields + in your BOM to locate your fields correctly. +* **Generic CSV file**: A generic CSV file. You can use this option if you use some different ECAD software or wanna create + your own CSV file. You will need to specify at least the designators, quantity and value fields in the CSV. In the next + step you can customize the mapping of the fields in Part-DB, if you have any special fields in your BOM to locate your + parts correctly. diff --git a/makefile b/makefile new file mode 100644 index 00000000..9041ba0f --- /dev/null +++ b/makefile @@ -0,0 +1,112 @@ +# PartDB Makefile for Test Environment Management + +.PHONY: help test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset deps-install + +# Default target +help: + @echo "PartDB Test Environment Management" + @echo "==================================" + @echo "" + @echo "Available targets:" + @echo " deps-install - Install PHP dependencies with unlimited memory" + @echo "" + @echo "Development Environment:" + @echo " dev-setup - Complete development environment setup (clean, create DB, migrate, warmup)" + @echo " dev-clean - Clean development cache and database files" + @echo " dev-db-create - Create development database (if not exists)" + @echo " dev-db-migrate - Run database migrations for development environment" + @echo " dev-cache-clear - Clear development cache" + @echo " dev-warmup - Warm up development cache" + @echo " dev-reset - Quick development reset (clean + migrate)" + @echo "" + @echo "Test Environment:" + @echo " test-setup - Complete test environment setup (clean, create DB, migrate, load fixtures)" + @echo " test-clean - Clean test cache and database files" + @echo " test-db-create - Create test database (if not exists)" + @echo " test-db-migrate - Run database migrations for test environment" + @echo " test-cache-clear- Clear test cache" + @echo " test-fixtures - Load test fixtures" + @echo " test-run - Run PHPUnit tests" + @echo "" + @echo " help - Show this help message" + +# Install PHP dependencies with unlimited memory +deps-install: + @echo "📦 Installing PHP dependencies..." + COMPOSER_MEMORY_LIMIT=-1 composer install + @echo "✅ Dependencies installed" + +# Complete test environment setup +test-setup: deps-install test-clean test-db-create test-db-migrate test-fixtures + @echo "✅ Test environment setup complete!" + +# Clean test environment +test-clean: + @echo "🧹 Cleaning test environment..." + rm -rf var/cache/test + rm -f var/app_test.db + @echo "✅ Test environment cleaned" + +# Create test database +test-db-create: + @echo "🗄️ Creating test database..." + -php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..." + +# Run database migrations for test environment +test-db-migrate: + @echo "🔄 Running database migrations..." + php -d memory_limit=1G bin/console doctrine:migrations:migrate -n --env test + +# Clear test cache +test-cache-clear: + @echo "🗑️ Clearing test cache..." + rm -rf var/cache/test + @echo "✅ Test cache cleared" + +# Load test fixtures +test-fixtures: + @echo "📦 Loading test fixtures..." + php bin/console partdb:fixtures:load -n --env test + +# Run PHPUnit tests +test-run: + @echo "🧪 Running tests..." + php bin/phpunit + +test-typecheck: + @echo "🧪 Running type checks..." + COMPOSER_MEMORY_LIMIT=-1 composer phpstan + +# Quick test reset (clean + migrate + fixtures, skip DB creation) +test-reset: test-cache-clear test-db-migrate test-fixtures + @echo "✅ Test environment reset complete!" + +# Development helpers +dev-setup: deps-install dev-clean dev-db-create dev-db-migrate dev-warmup + @echo "✅ Development environment setup complete!" + +dev-clean: + @echo "🧹 Cleaning development environment..." + rm -rf var/cache/dev + rm -f var/app_dev.db + @echo "✅ Development environment cleaned" + +dev-db-create: + @echo "🗄️ Creating development database..." + -php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..." + +dev-db-migrate: + @echo "🔄 Running database migrations..." + php -d memory_limit=1G bin/console doctrine:migrations:migrate -n --env dev + +dev-cache-clear: + @echo "🗑️ Clearing development cache..." + php -d memory_limit=1G bin/console cache:clear --env dev -n + @echo "✅ Development cache cleared" + +dev-warmup: + @echo "🔥 Warming up development cache..." + php -d memory_limit=1G bin/console cache:warmup --env dev -n + +dev-reset: dev-cache-clear dev-db-migrate + @echo "✅ Development environment reset complete!" \ No newline at end of file diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index 4950628b..90a6715b 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -58,12 +58,15 @@ use Symfony\Component\Form\FormError; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Contracts\Translation\TranslatorInterface; #[Route(path: '/label')] class LabelController extends AbstractController { - public function __construct(protected LabelGenerator $labelGenerator, protected EntityManagerInterface $em, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected RangeParser $rangeParser, protected TranslatorInterface $translator) + public function __construct(protected LabelGenerator $labelGenerator, protected EntityManagerInterface $em, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected RangeParser $rangeParser, protected TranslatorInterface $translator, + private readonly ValidatorInterface $validator + ) { } @@ -85,6 +88,7 @@ class LabelController extends AbstractController $form = $this->createForm(LabelDialogType::class, null, [ 'disable_options' => $disable_options, + 'profile' => $profile ]); //Try to parse given target_type and target_id @@ -120,13 +124,50 @@ class LabelController extends AbstractController goto render; } - $profile = new LabelProfile(); - $profile->setName($form->get('save_profile_name')->getData()); - $profile->setOptions($form_options); - $this->em->persist($profile); + $new_profile = new LabelProfile(); + $new_profile->setName($form->get('save_profile_name')->getData()); + $new_profile->setOptions($form_options); + + //Validate the profile name + $errors = $this->validator->validate($new_profile); + if (count($errors) > 0) { + foreach ($errors as $error) { + $form->get('save_profile_name')->addError(new FormError($error->getMessage())); + } + goto render; + } + + $this->em->persist($new_profile); $this->em->flush(); $this->addFlash('success', 'label_generator.profile_saved'); + return $this->redirectToRoute('label_dialog_profile', [ + 'profile' => $new_profile->getID(), + 'target_id' => (string) $form->get('target_id')->getData() + ]); + } + + //Check if the current profile should be updated + if ($form->has('update_profile') + && $form->get('update_profile')->isClicked() //@phpstan-ignore-line Phpstan does not recognize the isClicked method + && $profile instanceof LabelProfile + && $this->isGranted('edit', $profile)) { + //Update the profile options + $profile->setOptions($form_options); + + //Validate the profile name + $errors = $this->validator->validate($profile); + if (count($errors) > 0) { + foreach ($errors as $error) { + $this->addFlash('error', $error->getMessage()); + } + goto render; + } + + $this->em->persist($profile); + $this->em->flush(); + $this->addFlash('success', 'label_generator.profile_updated'); + return $this->redirectToRoute('label_dialog_profile', [ 'profile' => $profile->getID(), 'target_id' => (string) $form->get('target_id')->getData() diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index a64c1851..2a6d19ee 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -36,6 +36,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use League\Csv\SyntaxError; use Omines\DataTablesBundle\DataTableFactory; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -102,9 +103,14 @@ class ProjectController extends AbstractController $this->addFlash('success', 'project.build.flash.success'); return $this->redirect( - $request->get('_redirect', - $this->generateUrl('project_info', ['id' => $project->getID()] - ))); + $request->get( + '_redirect', + $this->generateUrl( + 'project_info', + ['id' => $project->getID()] + ) + ) + ); } $this->addFlash('error', 'project.build.flash.invalid_input'); @@ -120,9 +126,13 @@ class ProjectController extends AbstractController } #[Route(path: '/{id}/import_bom', name: 'project_import_bom', requirements: ['id' => '\d+'])] - public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project, - BOMImporter $BOMImporter, ValidatorInterface $validator): Response - { + public function importBOM( + Request $request, + EntityManagerInterface $entityManager, + Project $project, + BOMImporter $BOMImporter, + ValidatorInterface $validator + ): Response { $this->denyAccessUnlessGranted('edit', $project); $builder = $this->createFormBuilder(); @@ -138,6 +148,8 @@ class ProjectController extends AbstractController 'required' => true, 'choices' => [ 'project.bom_import.type.kicad_pcbnew' => 'kicad_pcbnew', + 'project.bom_import.type.kicad_schematic' => 'kicad_schematic', + 'project.bom_import.type.generic_csv' => 'generic_csv', ] ]); $builder->add('clear_existing_bom', CheckboxType::class, [ @@ -161,25 +173,40 @@ class ProjectController extends AbstractController $entityManager->flush(); } + $import_type = $form->get('type')->getData(); + try { + // For schematic imports, redirect to field mapping step + if (in_array($import_type, ['kicad_schematic', 'generic_csv'], true)) { + // Store file content and options in session for field mapping step + $file_content = $form->get('file')->getData()->getContent(); + $clear_existing = $form->get('clear_existing_bom')->getData(); + + $request->getSession()->set('bom_import_data', $file_content); + $request->getSession()->set('bom_import_clear', $clear_existing); + + return $this->redirectToRoute('project_import_bom_map_fields', ['id' => $project->getID()]); + } + + // For PCB imports, proceed directly $entries = $BOMImporter->importFileIntoProject($form->get('file')->getData(), $project, [ - 'type' => $form->get('type')->getData(), + 'type' => $import_type, ]); - //Validate the project entries + // Validate the project entries $errors = $validator->validateProperty($project, 'bom_entries'); - //If no validation errors occured, save the changes and redirect to edit page - if (count ($errors) === 0) { + // If no validation errors occurred, save the changes and redirect to edit page + if (count($errors) === 0) { $this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)])); $entityManager->flush(); return $this->redirectToRoute('project_edit', ['id' => $project->getID()]); } - //When we get here, there were validation errors + // When we get here, there were validation errors $this->addFlash('error', t('project.bom_import.flash.invalid_entries')); - } catch (\UnexpectedValueException|SyntaxError $e) { + } catch (\UnexpectedValueException | SyntaxError $e) { $this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); } } @@ -191,11 +218,267 @@ class ProjectController extends AbstractController ]); } + #[Route(path: '/{id}/import_bom/map_fields', name: 'project_import_bom_map_fields', requirements: ['id' => '\d+'])] + public function importBOMMapFields( + Request $request, + EntityManagerInterface $entityManager, + Project $project, + BOMImporter $BOMImporter, + ValidatorInterface $validator, + LoggerInterface $logger + ): Response { + $this->denyAccessUnlessGranted('edit', $project); + + // Get stored data from session + $file_content = $request->getSession()->get('bom_import_data'); + $clear_existing = $request->getSession()->get('bom_import_clear', false); + + + if (!$file_content) { + $this->addFlash('error', 'project.bom_import.flash.session_expired'); + return $this->redirectToRoute('project_import_bom', ['id' => $project->getID()]); + } + + // Detect fields and get suggestions + $detected_fields = $BOMImporter->detectFields($file_content); + $suggested_mapping = $BOMImporter->getSuggestedFieldMapping($detected_fields); + + // Create mapping of original field names to sanitized field names for template + $field_name_mapping = []; + foreach ($detected_fields as $field) { + $sanitized_field = preg_replace('/[^a-zA-Z0-9_-]/', '_', $field); + $field_name_mapping[$field] = $sanitized_field; + } + + // Create form for field mapping + $builder = $this->createFormBuilder(); + + // Add delimiter selection + $builder->add('delimiter', ChoiceType::class, [ + 'label' => 'project.bom_import.delimiter', + 'required' => true, + 'data' => ',', + 'choices' => [ + 'project.bom_import.delimiter.comma' => ',', + 'project.bom_import.delimiter.semicolon' => ';', + 'project.bom_import.delimiter.tab' => "\t", + ] + ]); + + // Get dynamic field mapping targets from BOMImporter + $available_targets = $BOMImporter->getAvailableFieldTargets(); + $target_fields = ['project.bom_import.field_mapping.ignore' => '']; + + foreach ($available_targets as $target_key => $target_info) { + $target_fields[$target_info['label']] = $target_key; + } + + foreach ($detected_fields as $field) { + // Sanitize field name for form use - replace invalid characters with underscores + $sanitized_field = preg_replace('/[^a-zA-Z0-9_-]/', '_', $field); + $builder->add('mapping_' . $sanitized_field, ChoiceType::class, [ + 'label' => $field, + 'required' => false, + 'choices' => $target_fields, + 'data' => $suggested_mapping[$field] ?? '', + ]); + } + + $builder->add('submit', SubmitType::class, [ + 'label' => 'project.bom_import.preview', + ]); + + $form = $builder->getForm(); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // Build field mapping array with priority support + $field_mapping = []; + $field_priorities = []; + $delimiter = $form->get('delimiter')->getData(); + + foreach ($detected_fields as $field) { + $sanitized_field = preg_replace('/[^a-zA-Z0-9_-]/', '_', $field); + $target = $form->get('mapping_' . $sanitized_field)->getData(); + if (!empty($target)) { + $field_mapping[$field] = $target; + + // Get priority from request (default to 10) + $priority = $request->request->get('priority_' . $sanitized_field, 10); + $field_priorities[$field] = (int) $priority; + } + } + + // Validate field mapping + $validation = $BOMImporter->validateFieldMapping($field_mapping, $detected_fields); + + if (!$validation['is_valid']) { + foreach ($validation['errors'] as $error) { + $this->addFlash('error', $error); + } + foreach ($validation['warnings'] as $warning) { + $this->addFlash('warning', $warning); + } + + return $this->render('projects/import_bom_map_fields.html.twig', [ + 'project' => $project, + 'form' => $form->createView(), + 'detected_fields' => $detected_fields, + 'suggested_mapping' => $suggested_mapping, + 'field_name_mapping' => $field_name_mapping, + ]); + } + + // Show warnings but continue + foreach ($validation['warnings'] as $warning) { + $this->addFlash('warning', $warning); + } + + try { + // Re-detect fields with chosen delimiter + $detected_fields = $BOMImporter->detectFields($file_content, $delimiter); + + // Clear existing BOM entries if requested + if ($clear_existing) { + $existing_count = $project->getBomEntries()->count(); + $logger->info('Clearing existing BOM entries', [ + 'existing_count' => $existing_count, + 'project_id' => $project->getID(), + ]); + $project->getBomEntries()->clear(); + $entityManager->flush(); + $logger->info('Existing BOM entries cleared'); + } else { + $existing_count = $project->getBomEntries()->count(); + $logger->info('Keeping existing BOM entries', [ + 'existing_count' => $existing_count, + 'project_id' => $project->getID(), + ]); + } + + // Validate data before importing + $validation_result = $BOMImporter->validateBOMData($file_content, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'field_priorities' => $field_priorities, + 'delimiter' => $delimiter, + ]); + + // Log validation results + $logger->info('BOM import validation completed', [ + 'total_entries' => $validation_result['total_entries'], + 'valid_entries' => $validation_result['valid_entries'], + 'invalid_entries' => $validation_result['invalid_entries'], + 'error_count' => count($validation_result['errors']), + 'warning_count' => count($validation_result['warnings']), + ]); + + // Show validation warnings to user + foreach ($validation_result['warnings'] as $warning) { + $this->addFlash('warning', $warning); + } + + // If there are validation errors, show them and stop + if (!empty($validation_result['errors'])) { + foreach ($validation_result['errors'] as $error) { + $this->addFlash('error', $error); + } + + return $this->render('projects/import_bom_map_fields.html.twig', [ + 'project' => $project, + 'form' => $form->createView(), + 'detected_fields' => $detected_fields, + 'suggested_mapping' => $suggested_mapping, + 'field_name_mapping' => $field_name_mapping, + 'validation_result' => $validation_result, + ]); + } + + // Import with field mapping and priorities (validation already passed) + $entries = $BOMImporter->stringToBOMEntries($file_content, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'field_priorities' => $field_priorities, + 'delimiter' => $delimiter, + ]); + + // Log entry details for debugging + $logger->info('BOM entries created', [ + 'total_entries' => count($entries), + ]); + + foreach ($entries as $index => $entry) { + $logger->debug("BOM entry {$index}", [ + 'name' => $entry->getName(), + 'mountnames' => $entry->getMountnames(), + 'quantity' => $entry->getQuantity(), + 'comment' => $entry->getComment(), + 'part_id' => $entry->getPart()?->getID(), + ]); + } + + // Assign entries to project + $logger->info('Adding BOM entries to project', [ + 'entries_count' => count($entries), + 'project_id' => $project->getID(), + ]); + + foreach ($entries as $index => $entry) { + $logger->debug("Adding BOM entry {$index} to project", [ + 'name' => $entry->getName(), + 'part_id' => $entry->getPart()?->getID(), + 'quantity' => $entry->getQuantity(), + ]); + $project->addBomEntry($entry); + } + + // Validate the project entries (includes collection constraints) + $errors = $validator->validateProperty($project, 'bom_entries'); + + // If no validation errors occurred, save and redirect + if (count($errors) === 0) { + $this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)])); + $entityManager->flush(); + + // Clear session data + $request->getSession()->remove('bom_import_data'); + $request->getSession()->remove('bom_import_clear'); + + return $this->redirectToRoute('project_edit', ['id' => $project->getID()]); + } + + // When we get here, there were validation errors + $this->addFlash('error', t('project.bom_import.flash.invalid_entries')); + + //Print validation errors to log for debugging + foreach ($errors as $error) { + $logger->error('BOM entry validation error', [ + 'message' => $error->getMessage(), + 'invalid_value' => $error->getInvalidValue(), + ]); + //And show as flash message + $this->addFlash('error', $error->getMessage(),); + } + + } catch (\UnexpectedValueException | SyntaxError $e) { + $this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); + } + } + + return $this->render('projects/import_bom_map_fields.html.twig', [ + 'project' => $project, + 'form' => $form, + 'detected_fields' => $detected_fields, + 'suggested_mapping' => $suggested_mapping, + 'field_name_mapping' => $field_name_mapping, + ]); + } + #[Route(path: '/add_parts', name: 'project_add_parts_no_id')] #[Route(path: '/{id}/add_parts', name: 'project_add_parts', requirements: ['id' => '\d+'])] public function addPart(Request $request, EntityManagerInterface $entityManager, ?Project $project): Response { - if($project instanceof Project) { + if ($project instanceof Project) { $this->denyAccessUnlessGranted('edit', $project); } else { $this->denyAccessUnlessGranted('@projects.edit'); @@ -242,7 +525,7 @@ class ProjectController extends AbstractController $data = $form->getData(); $bom_entries = $data['bom_entries']; - foreach ($bom_entries as $bom_entry){ + foreach ($bom_entries as $bom_entry) { $target_project->addBOMEntry($bom_entry); } diff --git a/src/DataFixtures/CurrencyFixtures.php b/src/DataFixtures/CurrencyFixtures.php new file mode 100644 index 00000000..2de5b277 --- /dev/null +++ b/src/DataFixtures/CurrencyFixtures.php @@ -0,0 +1,64 @@ +. + */ + +declare(strict_types=1); + + +namespace App\DataFixtures; + +use App\Entity\PriceInformations\Currency; +use Brick\Math\BigDecimal; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Persistence\ObjectManager; + +class CurrencyFixtures extends Fixture +{ + public function load(ObjectManager $manager): void + { + $currency1 = new Currency(); + $currency1->setName('US-Dollar'); + $currency1->setIsoCode('USD'); + $manager->persist($currency1); + + $currency2 = new Currency(); + $currency2->setName('Swiss Franc'); + $currency2->setIsoCode('CHF'); + $currency2->setExchangeRate(BigDecimal::of('0.91')); + $manager->persist($currency2); + + $currency3 = new Currency(); + $currency3->setName('Great British Pound'); + $currency3->setIsoCode('GBP'); + $currency3->setExchangeRate(BigDecimal::of('0.78')); + $manager->persist($currency3); + + $currency7 = new Currency(); + $currency7->setName('Test Currency with long name'); + $currency7->setIsoCode('CNY'); + $manager->persist($currency7); + + $manager->flush(); + + + //Ensure that currency 7 gets ID 7 + $manager->getRepository(Currency::class)->changeID($currency7, 7); + $manager->flush(); + } +} diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php index f2710b19..d79d01f6 100644 --- a/src/Form/LabelSystem/LabelDialogType.php +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -87,6 +87,16 @@ class LabelDialogType extends AbstractType ] ]); + if ($options['profile'] !== null) { + $builder->add('update_profile', SubmitType::class, [ + 'label' => 'label_generator.update_profile', + 'disabled' => !$this->security->isGranted('edit', $options['profile']), + 'attr' => [ + 'class' => 'btn btn-outline-success' + ] + ]); + } + $builder->add('update', SubmitType::class, [ 'label' => 'label_generator.update', ]); @@ -97,5 +107,6 @@ class LabelDialogType extends AbstractType parent::configureOptions($resolver); $resolver->setDefault('mapped', false); $resolver->setDefault('disable_options', false); + $resolver->setDefault('profile', null); } } diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index c2b17053..bd7ae4df 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -41,6 +41,7 @@ use App\Entity\Attachments\UserAttachment; use RuntimeException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use function in_array; @@ -56,7 +57,7 @@ final class AttachmentVoter extends Voter { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { //This voter only works for attachments @@ -65,7 +66,8 @@ final class AttachmentVoter extends Voter } if ($attribute === 'show_private') { - return $this->helper->isGranted($token, 'attachments', 'show_private'); + $vote?->addReason('User is not allowed to view private attachments.'); + return $this->helper->isGranted($token, 'attachments', 'show_private', $vote); } @@ -111,7 +113,8 @@ final class AttachmentVoter extends Voter throw new RuntimeException('Encountered unknown Parameter type: ' . $subject); } - return $this->helper->isGranted($token, $param, $this->mapOperation($attribute)); + $vote?->addReason('User is not allowed to '.$this->mapOperation($attribute).' attachments of type '.$param.'.'); + return $this->helper->isGranted($token, $param, $this->mapOperation($attribute), $vote); } return false; diff --git a/src/Security/Voter/GroupVoter.php b/src/Security/Voter/GroupVoter.php index 34839d38..f2ce6953 100644 --- a/src/Security/Voter/GroupVoter.php +++ b/src/Security/Voter/GroupVoter.php @@ -25,6 +25,7 @@ namespace App\Security\Voter; use App\Entity\UserSystem\Group; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -43,9 +44,9 @@ final class GroupVoter extends Voter * * @param string $attribute */ - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->helper->isGranted($token, 'groups', $attribute); + return $this->helper->isGranted($token, 'groups', $attribute, $vote); } /** diff --git a/src/Security/Voter/ImpersonateUserVoter.php b/src/Security/Voter/ImpersonateUserVoter.php index edf55c62..1f8a70c6 100644 --- a/src/Security/Voter/ImpersonateUserVoter.php +++ b/src/Security/Voter/ImpersonateUserVoter.php @@ -26,6 +26,7 @@ namespace App\Security\Voter; use App\Entity\UserSystem\User; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\User\UserInterface; @@ -47,9 +48,16 @@ final class ImpersonateUserVoter extends Voter && $subject instanceof UserInterface; } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->helper->isGranted($token, 'users', 'impersonate'); + $result = $this->helper->isGranted($token, 'users', 'impersonate'); + + if ($result === false) { + $vote?->addReason('User is not allowed to impersonate other users.'); + $this->helper->addReason($vote, 'users', 'impersonate'); + } + + return $result; } public function supportsAttribute(string $attribute): bool @@ -61,4 +69,4 @@ final class ImpersonateUserVoter extends Voter { return is_a($subjectType, User::class, true); } -} \ No newline at end of file +} diff --git a/src/Security/Voter/LabelProfileVoter.php b/src/Security/Voter/LabelProfileVoter.php index 47505bf9..1687bf45 100644 --- a/src/Security/Voter/LabelProfileVoter.php +++ b/src/Security/Voter/LabelProfileVoter.php @@ -44,6 +44,7 @@ namespace App\Security\Voter; use App\Entity\LabelSystem\LabelProfile; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -58,14 +59,15 @@ final class LabelProfileVoter extends Voter 'delete' => 'delete_profiles', 'show_history' => 'show_history', 'revert_element' => 'revert_element', + 'import' => 'import', ]; public function __construct(private readonly VoterHelper $helper) {} - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - return $this->helper->isGranted($token, 'labels', self::MAPPING[$attribute]); + return $this->helper->isGranted($token, 'labels', self::MAPPING[$attribute], $vote); } protected function supports($attribute, $subject): bool diff --git a/src/Security/Voter/LogEntryVoter.php b/src/Security/Voter/LogEntryVoter.php index 08bc3b70..dcb75a7a 100644 --- a/src/Security/Voter/LogEntryVoter.php +++ b/src/Security/Voter/LogEntryVoter.php @@ -26,6 +26,7 @@ use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LogSystem\AbstractLogEntry; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -39,7 +40,7 @@ final class LogEntryVoter extends Voter { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); @@ -48,19 +49,19 @@ final class LogEntryVoter extends Voter } if ('delete' === $attribute) { - return $this->helper->isGranted($token, 'system', 'delete_logs'); + return $this->helper->isGranted($token, 'system', 'delete_logs', $vote); } if ('read' === $attribute) { //Allow read of the users own log entries if ( $subject->getUser() === $user - && $this->helper->isGranted($token, 'self', 'show_logs') + && $this->helper->isGranted($token, 'self', 'show_logs', $vote) ) { return true; } - return $this->helper->isGranted($token, 'system', 'show_logs'); + return $this->helper->isGranted($token, 'system', 'show_logs', $vote); } if ('show_details' === $attribute) { diff --git a/src/Security/Voter/OrderdetailVoter.php b/src/Security/Voter/OrderdetailVoter.php index 20843b9a..3bb2a3a3 100644 --- a/src/Security/Voter/OrderdetailVoter.php +++ b/src/Security/Voter/OrderdetailVoter.php @@ -46,6 +46,7 @@ use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -59,7 +60,7 @@ final class OrderdetailVoter extends Voter protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { if (! is_a($subject, Orderdetail::class, true)) { throw new \RuntimeException('This voter can only handle Orderdetail objects!'); @@ -75,7 +76,7 @@ final class OrderdetailVoter extends Voter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index 8ee2b9f5..f59bdeaf 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -39,6 +39,7 @@ use App\Entity\Parameters\StorageLocationParameter; use App\Entity\Parameters\SupplierParameter; use RuntimeException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -53,7 +54,7 @@ final class ParameterVoter extends Voter { } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { //return $this->resolver->inherit($user, 'attachments', $attribute) ?? false; @@ -108,7 +109,7 @@ final class ParameterVoter extends Voter throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? $subject::class : $subject)); } - return $this->helper->isGranted($token, $param, $attribute); + return $this->helper->isGranted($token, $param, $attribute, $vote); } protected function supports(string $attribute, $subject): bool diff --git a/src/Security/Voter/PartAssociationVoter.php b/src/Security/Voter/PartAssociationVoter.php index 7678b67a..f1eb83c7 100644 --- a/src/Security/Voter/PartAssociationVoter.php +++ b/src/Security/Voter/PartAssociationVoter.php @@ -46,6 +46,7 @@ use App\Services\UserSystem\VoterHelper; use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -61,7 +62,7 @@ final class PartAssociationVoter extends Voter protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { if (!is_string($subject) && !$subject instanceof PartAssociation) { throw new \RuntimeException('Invalid subject type!'); @@ -77,7 +78,7 @@ final class PartAssociationVoter extends Voter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getOwner() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index a64473c8..87c3d135 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -46,6 +46,7 @@ use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -59,13 +60,13 @@ final class PartLotVoter extends Voter protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); if (in_array($attribute, ['withdraw', 'add', 'move'], true)) { - $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute); + $base_permission = $this->helper->isGranted($token, 'parts_stock', $attribute, $vote); $lot_permission = true; //If the lot has an owner, we need to check if the user is the owner of the lot to be allowed to withdraw it. @@ -73,6 +74,10 @@ final class PartLotVoter extends Voter $lot_permission = $subject->getOwner() === $user || $subject->getOwner()->getID() === $user->getID(); } + if (!$lot_permission) { + $vote->addReason('User is not the owner of the lot.'); + } + return $base_permission && $lot_permission; } @@ -86,7 +91,7 @@ final class PartLotVoter extends Voter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getPart() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index ef70b6ce..159e6893 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -25,6 +25,7 @@ namespace App\Security\Voter; use App\Entity\Parts\Part; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -52,10 +53,9 @@ final class PartVoter extends Voter return false; } - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { - //Null concealing operator means, that no - return $this->helper->isGranted($token, 'parts', $attribute); + return $this->helper->isGranted($token, 'parts', $attribute, $vote); } public function supportsAttribute(string $attribute): bool diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php index c6ec1b3d..8c304d86 100644 --- a/src/Security/Voter/PermissionVoter.php +++ b/src/Security/Voter/PermissionVoter.php @@ -24,6 +24,7 @@ namespace App\Security\Voter; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -39,12 +40,17 @@ final class PermissionVoter extends Voter } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { $attribute = ltrim($attribute, '@'); [$perm, $op] = explode('.', $attribute); - return $this->helper->isGranted($token, $perm, $op); + $result = $this->helper->isGranted($token, $perm, $op); + if ($result === false) { + $this->helper->addReason($vote, $perm, $op); + } + + return $result; } public function supportsAttribute(string $attribute): bool diff --git a/src/Security/Voter/PricedetailVoter.php b/src/Security/Voter/PricedetailVoter.php index 681b73b7..ca86f1ce 100644 --- a/src/Security/Voter/PricedetailVoter.php +++ b/src/Security/Voter/PricedetailVoter.php @@ -47,6 +47,7 @@ use App\Entity\PriceInformations\Orderdetail; use App\Entity\Parts\Part; use App\Entity\PriceInformations\Pricedetail; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** @@ -60,7 +61,7 @@ final class PricedetailVoter extends Voter protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $operation = match ($attribute) { 'read' => 'read', @@ -72,7 +73,7 @@ final class PricedetailVoter extends Voter //If we have no part associated use the generic part permission if (is_string($subject) || !$subject->getOrderdetail() instanceof Orderdetail || !$subject->getOrderdetail()->getPart() instanceof Part) { - return $this->helper->isGranted($token, 'parts', $operation); + return $this->helper->isGranted($token, 'parts', $operation, $vote); } //Otherwise vote on the part diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index 2417b796..ad0299a7 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -33,6 +33,7 @@ use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Currency; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use function is_object; @@ -113,10 +114,10 @@ final class StructureVoter extends Voter * * @param string $attribute */ - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $permission_name = $this->instanceToPermissionName($subject); //Just resolve the permission - return $this->helper->isGranted($token, $permission_name, $attribute); + return $this->helper->isGranted($token, $permission_name, $attribute, $vote); } } diff --git a/src/Security/Voter/UserVoter.php b/src/Security/Voter/UserVoter.php index b41c1a40..97f8e4fb 100644 --- a/src/Security/Voter/UserVoter.php +++ b/src/Security/Voter/UserVoter.php @@ -26,6 +26,7 @@ use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use App\Services\UserSystem\VoterHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use function in_array; @@ -79,7 +80,7 @@ final class UserVoter extends Voter * * @param string $attribute */ - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $this->helper->resolveUser($token); @@ -97,7 +98,7 @@ final class UserVoter extends Voter if (($subject instanceof User) && $subject->getID() === $user->getID() && $this->helper->isValidOperation('self', $attribute)) { //Then we also need to check the self permission - $tmp = $this->helper->isGranted($token, 'self', $attribute); + $tmp = $this->helper->isGranted($token, 'self', $attribute, $vote); //But if the self value is not allowed then use just the user value: if ($tmp) { return $tmp; @@ -106,7 +107,7 @@ final class UserVoter extends Voter //Else just check user permission: if ($this->helper->isValidOperation('users', $attribute)) { - return $this->helper->isGranted($token, 'users', $attribute); + return $this->helper->isGranted($token, 'users', $attribute, $vote); } return false; diff --git a/src/Services/ImportExportSystem/BOMImporter.php b/src/Services/ImportExportSystem/BOMImporter.php index d4876445..862fa463 100644 --- a/src/Services/ImportExportSystem/BOMImporter.php +++ b/src/Services/ImportExportSystem/BOMImporter.php @@ -22,10 +22,13 @@ declare(strict_types=1); */ namespace App\Services\ImportExportSystem; +use App\Entity\Parts\Part; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; +use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; use League\Csv\Reader; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -44,14 +47,25 @@ class BOMImporter 5 => 'Supplier and ref', ]; - public function __construct() - { + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly LoggerInterface $logger, + private readonly BOMValidationService $validationService + ) { } protected function configureOptions(OptionsResolver $resolver): OptionsResolver { $resolver->setRequired('type'); - $resolver->setAllowedValues('type', ['kicad_pcbnew']); + $resolver->setAllowedValues('type', ['kicad_pcbnew', 'kicad_schematic']); + + // For flexible schematic import with field mapping + $resolver->setDefined(['field_mapping', 'field_priorities', 'delimiter']); + $resolver->setDefault('delimiter', ','); + $resolver->setDefault('field_priorities', []); + $resolver->setAllowedTypes('field_mapping', 'array'); + $resolver->setAllowedTypes('field_priorities', 'array'); + $resolver->setAllowedTypes('delimiter', 'string'); return $resolver; } @@ -82,6 +96,23 @@ class BOMImporter return $this->stringToBOMEntries($file->getContent(), $options); } + /** + * Validate BOM data before importing + * @return array Validation result with errors, warnings, and info + */ + public function validateBOMData(string $data, array $options): array + { + $resolver = new OptionsResolver(); + $resolver = $this->configureOptions($resolver); + $options = $resolver->resolve($options); + + return match ($options['type']) { + 'kicad_pcbnew' => $this->validateKiCADPCB($data), + 'kicad_schematic' => $this->validateKiCADSchematicData($data, $options), + default => throw new InvalidArgumentException('Invalid import type!'), + }; + } + /** * Import string data into an array of BOM entries, which are not yet assigned to a project. * @param string $data The data to import @@ -95,12 +126,13 @@ class BOMImporter $options = $resolver->resolve($options); return match ($options['type']) { - 'kicad_pcbnew' => $this->parseKiCADPCB($data, $options), + 'kicad_pcbnew' => $this->parseKiCADPCB($data), + 'kicad_schematic' => $this->parseKiCADSchematic($data, $options), default => throw new InvalidArgumentException('Invalid import type!'), }; } - private function parseKiCADPCB(string $data, array $options = []): array + private function parseKiCADPCB(string $data): array { $csv = Reader::createFromString($data); $csv->setDelimiter(';'); @@ -113,17 +145,17 @@ class BOMImporter $entry = $this->normalizeColumnNames($entry); //Ensure that the entry has all required fields - if (!isset ($entry['Designator'])) { - throw new \UnexpectedValueException('Designator missing at line '.($offset + 1).'!'); + if (!isset($entry['Designator'])) { + throw new \UnexpectedValueException('Designator missing at line ' . ($offset + 1) . '!'); } - if (!isset ($entry['Package'])) { - throw new \UnexpectedValueException('Package missing at line '.($offset + 1).'!'); + if (!isset($entry['Package'])) { + throw new \UnexpectedValueException('Package missing at line ' . ($offset + 1) . '!'); } - if (!isset ($entry['Designation'])) { - throw new \UnexpectedValueException('Designation missing at line '.($offset + 1).'!'); + if (!isset($entry['Designation'])) { + throw new \UnexpectedValueException('Designation missing at line ' . ($offset + 1) . '!'); } - if (!isset ($entry['Quantity'])) { - throw new \UnexpectedValueException('Quantity missing at line '.($offset + 1).'!'); + if (!isset($entry['Quantity'])) { + throw new \UnexpectedValueException('Quantity missing at line ' . ($offset + 1) . '!'); } $bom_entry = new ProjectBOMEntry(); @@ -138,6 +170,63 @@ class BOMImporter return $bom_entries; } + /** + * Validate KiCad PCB data + */ + private function validateKiCADPCB(string $data): array + { + $csv = Reader::createFromString($data); + $csv->setDelimiter(';'); + $csv->setHeaderOffset(0); + + $mapped_entries = []; + + foreach ($csv->getRecords() as $offset => $entry) { + // Translate the german field names to english + $entry = $this->normalizeColumnNames($entry); + $mapped_entries[] = $entry; + } + + return $this->validationService->validateBOMEntries($mapped_entries); + } + + /** + * Validate KiCad schematic data + */ + private function validateKiCADSchematicData(string $data, array $options): array + { + $delimiter = $options['delimiter'] ?? ','; + $field_mapping = $options['field_mapping'] ?? []; + $field_priorities = $options['field_priorities'] ?? []; + + // Handle potential BOM (Byte Order Mark) at the beginning + $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); + + $csv = Reader::createFromString($data); + $csv->setDelimiter($delimiter); + $csv->setHeaderOffset(0); + + // Handle quoted fields properly + $csv->setEscape('\\'); + $csv->setEnclosure('"'); + + $mapped_entries = []; + + foreach ($csv->getRecords() as $offset => $entry) { + // Apply field mapping to translate column names + $mapped_entry = $this->applyFieldMapping($entry, $field_mapping, $field_priorities); + + // Extract footprint package name if it contains library prefix + if (isset($mapped_entry['Package']) && str_contains($mapped_entry['Package'], ':')) { + $mapped_entry['Package'] = explode(':', $mapped_entry['Package'], 2)[1]; + } + + $mapped_entries[] = $mapped_entry; + } + + return $this->validationService->validateBOMEntries($mapped_entries, $options); + } + /** * This function uses the order of the fields in the CSV files to make them locale independent. * @param array $entry @@ -160,4 +249,482 @@ class BOMImporter return $out; } + + /** + * Parse KiCad schematic BOM with flexible field mapping + */ + private function parseKiCADSchematic(string $data, array $options = []): array + { + $delimiter = $options['delimiter'] ?? ','; + $field_mapping = $options['field_mapping'] ?? []; + $field_priorities = $options['field_priorities'] ?? []; + + // Handle potential BOM (Byte Order Mark) at the beginning + $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); + + $csv = Reader::createFromString($data); + $csv->setDelimiter($delimiter); + $csv->setHeaderOffset(0); + + // Handle quoted fields properly + $csv->setEscape('\\'); + $csv->setEnclosure('"'); + + $bom_entries = []; + $entries_by_key = []; // Track entries by name+part combination + $mapped_entries = []; // Collect all mapped entries for validation + + foreach ($csv->getRecords() as $offset => $entry) { + // Apply field mapping to translate column names + $mapped_entry = $this->applyFieldMapping($entry, $field_mapping, $field_priorities); + + // Extract footprint package name if it contains library prefix + if (isset($mapped_entry['Package']) && str_contains($mapped_entry['Package'], ':')) { + $mapped_entry['Package'] = explode(':', $mapped_entry['Package'], 2)[1]; + } + + $mapped_entries[] = $mapped_entry; + } + + // Validate all entries before processing + $validation_result = $this->validationService->validateBOMEntries($mapped_entries, $options); + + // Log validation results + $this->logger->info('BOM import validation completed', [ + 'total_entries' => $validation_result['total_entries'], + 'valid_entries' => $validation_result['valid_entries'], + 'invalid_entries' => $validation_result['invalid_entries'], + 'error_count' => count($validation_result['errors']), + 'warning_count' => count($validation_result['warnings']), + ]); + + // If there are validation errors, throw an exception with detailed messages + if (!empty($validation_result['errors'])) { + $error_message = $this->validationService->getErrorMessage($validation_result); + throw new \UnexpectedValueException("BOM import validation failed:\n" . $error_message); + } + + // Process validated entries + foreach ($mapped_entries as $offset => $mapped_entry) { + + // Set name - prefer MPN, fall back to Value, then default format + $mpn = trim($mapped_entry['MPN'] ?? ''); + $designation = trim($mapped_entry['Designation'] ?? ''); + $value = trim($mapped_entry['Value'] ?? ''); + + // Use the first non-empty value, or 'Unknown Component' if all are empty + $name = ''; + if (!empty($mpn)) { + $name = $mpn; + } elseif (!empty($designation)) { + $name = $designation; + } elseif (!empty($value)) { + $name = $value; + } else { + $name = 'Unknown Component'; + } + + if (isset($mapped_entry['Package']) && !empty(trim($mapped_entry['Package']))) { + $name .= ' (' . trim($mapped_entry['Package']) . ')'; + } + + // Set mountnames and quantity + // The Designator field contains comma-separated mount names for all instances + $designator = trim($mapped_entry['Designator']); + $quantity = (float) $mapped_entry['Quantity']; + + // Get mountnames array (validation already ensured they match quantity) + $mountnames_array = array_map('trim', explode(',', $designator)); + + // Try to link existing Part-DB part if ID is provided + $part = null; + if (isset($mapped_entry['Part-DB ID']) && !empty($mapped_entry['Part-DB ID'])) { + $partDbId = (int) $mapped_entry['Part-DB ID']; + $existingPart = $this->entityManager->getRepository(Part::class)->find($partDbId); + + if ($existingPart) { + $part = $existingPart; + // Update name with actual part name + $name = $existingPart->getName(); + } + } + + // Create unique key for this entry (name + part ID) + $entry_key = $name . '|' . ($part ? $part->getID() : 'null'); + + // Check if we already have an entry with the same name and part + if (isset($entries_by_key[$entry_key])) { + // Merge with existing entry + $existing_entry = $entries_by_key[$entry_key]; + + // Combine mountnames + $existing_mountnames = $existing_entry->getMountnames(); + $combined_mountnames = $existing_mountnames . ',' . $designator; + $existing_entry->setMountnames($combined_mountnames); + + // Add quantities + $existing_quantity = $existing_entry->getQuantity(); + $existing_entry->setQuantity($existing_quantity + $quantity); + + $this->logger->info('Merged duplicate BOM entry', [ + 'name' => $name, + 'part_id' => $part ? $part->getID() : null, + 'original_quantity' => $existing_quantity, + 'added_quantity' => $quantity, + 'new_quantity' => $existing_quantity + $quantity, + 'original_mountnames' => $existing_mountnames, + 'added_mountnames' => $designator, + ]); + + continue; // Skip creating new entry + } + + // Create new BOM entry + $bom_entry = new ProjectBOMEntry(); + $bom_entry->setName($name); + $bom_entry->setMountnames($designator); + $bom_entry->setQuantity($quantity); + + if ($part) { + $bom_entry->setPart($part); + } + + // Set comment with additional info + $comment_parts = []; + if (isset($mapped_entry['Value']) && $mapped_entry['Value'] !== ($mapped_entry['MPN'] ?? '')) { + $comment_parts[] = 'Value: ' . $mapped_entry['Value']; + } + if (isset($mapped_entry['MPN'])) { + $comment_parts[] = 'MPN: ' . $mapped_entry['MPN']; + } + if (isset($mapped_entry['Manufacturer'])) { + $comment_parts[] = 'Manf: ' . $mapped_entry['Manufacturer']; + } + if (isset($mapped_entry['LCSC'])) { + $comment_parts[] = 'LCSC: ' . $mapped_entry['LCSC']; + } + if (isset($mapped_entry['Supplier and ref'])) { + $comment_parts[] = $mapped_entry['Supplier and ref']; + } + + if ($part) { + $comment_parts[] = "Part-DB ID: " . $part->getID(); + } elseif (isset($mapped_entry['Part-DB ID']) && !empty($mapped_entry['Part-DB ID'])) { + $comment_parts[] = "Part-DB ID: " . $mapped_entry['Part-DB ID'] . " (NOT FOUND)"; + } + + $bom_entry->setComment(implode(', ', $comment_parts)); + + $bom_entries[] = $bom_entry; + $entries_by_key[$entry_key] = $bom_entry; + } + + return $bom_entries; + } + + /** + * Get all available field mapping targets with descriptions + */ + public function getAvailableFieldTargets(): array + { + $targets = [ + 'Designator' => [ + 'label' => 'Designator', + 'description' => 'Component reference designators (e.g., R1, C2, U3)', + 'required' => true, + 'multiple' => false, + ], + 'Quantity' => [ + 'label' => 'Quantity', + 'description' => 'Number of components', + 'required' => true, + 'multiple' => false, + ], + 'Designation' => [ + 'label' => 'Designation', + 'description' => 'Component designation/part number', + 'required' => false, + 'multiple' => true, + ], + 'Value' => [ + 'label' => 'Value', + 'description' => 'Component value (e.g., 10k, 100nF)', + 'required' => false, + 'multiple' => true, + ], + 'Package' => [ + 'label' => 'Package', + 'description' => 'Component package/footprint', + 'required' => false, + 'multiple' => true, + ], + 'MPN' => [ + 'label' => 'MPN', + 'description' => 'Manufacturer Part Number', + 'required' => false, + 'multiple' => true, + ], + 'Manufacturer' => [ + 'label' => 'Manufacturer', + 'description' => 'Component manufacturer name', + 'required' => false, + 'multiple' => true, + ], + 'Part-DB ID' => [ + 'label' => 'Part-DB ID', + 'description' => 'Existing Part-DB part ID for linking', + 'required' => false, + 'multiple' => false, + ], + 'Comment' => [ + 'label' => 'Comment', + 'description' => 'Additional component information', + 'required' => false, + 'multiple' => true, + ], + ]; + + // Add dynamic supplier fields based on available suppliers in the database + $suppliers = $this->entityManager->getRepository(\App\Entity\Parts\Supplier::class)->findAll(); + foreach ($suppliers as $supplier) { + $supplierName = $supplier->getName(); + $targets[$supplierName . ' SPN'] = [ + 'label' => $supplierName . ' SPN', + 'description' => "Supplier part number for {$supplierName}", + 'required' => false, + 'multiple' => true, + 'supplier_id' => $supplier->getID(), + ]; + } + + return $targets; + } + + /** + * Get suggested field mappings based on common field names + */ + public function getSuggestedFieldMapping(array $detected_fields): array + { + $suggestions = []; + + $field_patterns = [ + 'Part-DB ID' => ['part-db id', 'partdb_id', 'part_db_id', 'db_id', 'partdb'], + 'Designator' => ['reference', 'ref', 'designator', 'component', 'comp'], + 'Quantity' => ['qty', 'quantity', 'count', 'number', 'amount'], + 'Value' => ['value', 'val', 'component_value'], + 'Designation' => ['designation', 'part_number', 'partnumber', 'part'], + 'Package' => ['footprint', 'package', 'housing', 'fp'], + 'MPN' => ['mpn', 'part_number', 'partnumber', 'manf#', 'mfr_part_number', 'manufacturer_part'], + 'Manufacturer' => ['manufacturer', 'manf', 'mfr', 'brand', 'vendor'], + 'Comment' => ['comment', 'comments', 'note', 'notes', 'description'], + ]; + + // Add supplier-specific patterns + $suppliers = $this->entityManager->getRepository(\App\Entity\Parts\Supplier::class)->findAll(); + foreach ($suppliers as $supplier) { + $supplierName = $supplier->getName(); + $supplierLower = strtolower($supplierName); + + // Create patterns for each supplier + $field_patterns[$supplierName . ' SPN'] = [ + $supplierLower, + $supplierLower . '#', + $supplierLower . '_part', + $supplierLower . '_number', + $supplierLower . 'pn', + $supplierLower . '_spn', + $supplierLower . ' spn', + // Common abbreviations + $supplierLower === 'mouser' ? 'mouser' : null, + $supplierLower === 'digikey' ? 'dk' : null, + $supplierLower === 'farnell' ? 'farnell' : null, + $supplierLower === 'rs' ? 'rs' : null, + $supplierLower === 'lcsc' ? 'lcsc' : null, + ]; + + // Remove null values + $field_patterns[$supplierName . ' SPN'] = array_filter($field_patterns[$supplierName . ' SPN'], fn($value) => $value !== null); + } + + foreach ($detected_fields as $field) { + $field_lower = strtolower(trim($field)); + + foreach ($field_patterns as $target => $patterns) { + foreach ($patterns as $pattern) { + if (str_contains($field_lower, $pattern)) { + $suggestions[$field] = $target; + break 2; // Break both loops + } + } + } + } + + return $suggestions; + } + + /** + * Validate field mapping configuration + */ + public function validateFieldMapping(array $field_mapping, array $detected_fields): array + { + $errors = []; + $warnings = []; + $available_targets = $this->getAvailableFieldTargets(); + + // Check for required fields + $mapped_targets = array_values($field_mapping); + $required_fields = ['Designator', 'Quantity']; + + foreach ($required_fields as $required) { + if (!in_array($required, $mapped_targets, true)) { + $errors[] = "Required field '{$required}' is not mapped from any CSV column."; + } + } + + // Check for invalid target fields + foreach ($field_mapping as $csv_field => $target) { + if (!empty($target) && !isset($available_targets[$target])) { + $errors[] = "Invalid target field '{$target}' for CSV field '{$csv_field}'."; + } + } + + // Check for unmapped fields (warnings) + $unmapped_fields = array_diff($detected_fields, array_keys($field_mapping)); + if (!empty($unmapped_fields)) { + $warnings[] = "The following CSV fields are not mapped: " . implode(', ', $unmapped_fields); + } + + return [ + 'errors' => $errors, + 'warnings' => $warnings, + 'is_valid' => empty($errors), + ]; + } + + /** + * Apply field mapping with support for multiple fields and priority + */ + private function applyFieldMapping(array $entry, array $field_mapping, array $field_priorities = []): array + { + $mapped = []; + $field_groups = []; + + // Group fields by target with priority information + foreach ($field_mapping as $csv_field => $target) { + if (!empty($target)) { + if (!isset($field_groups[$target])) { + $field_groups[$target] = []; + } + $priority = $field_priorities[$csv_field] ?? 10; + $field_groups[$target][] = [ + 'field' => $csv_field, + 'priority' => $priority, + 'value' => $entry[$csv_field] ?? '' + ]; + } + } + + // Process each target field + foreach ($field_groups as $target => $field_data) { + // Sort by priority (lower number = higher priority) + usort($field_data, function ($a, $b) { + return $a['priority'] <=> $b['priority']; + }); + + $values = []; + $non_empty_values = []; + + // Collect all non-empty values for this target + foreach ($field_data as $data) { + $value = trim($data['value']); + if (!empty($value)) { + $non_empty_values[] = $value; + } + $values[] = $value; + } + + // Use the first non-empty value (highest priority) + if (!empty($non_empty_values)) { + $mapped[$target] = $non_empty_values[0]; + + // If multiple non-empty values exist, add alternatives to comment + if (count($non_empty_values) > 1) { + $mapped[$target . '_alternatives'] = array_slice($non_empty_values, 1); + } + } + } + + return $mapped; + } + + /** + * Detect available fields in CSV data for field mapping UI + */ + public function detectFields(string $data, ?string $delimiter = null): array + { + if ($delimiter === null) { + // Detect delimiter by counting occurrences in the first row (header) + $delimiters = [',', ';', "\t"]; + $lines = explode("\n", $data, 2); + $header_line = $lines[0] ?? ''; + $delimiter_counts = []; + foreach ($delimiters as $delim) { + $delimiter_counts[$delim] = substr_count($header_line, $delim); + } + // Choose the delimiter with the highest count, default to comma if all are zero + $max_count = max($delimiter_counts); + $delimiter = array_search($max_count, $delimiter_counts, true); + if ($max_count === 0 || $delimiter === false) { + $delimiter = ','; + } + } + // Handle potential BOM (Byte Order Mark) at the beginning + $data = preg_replace('/^\xEF\xBB\xBF/', '', $data); + + // Get first line only for header detection + $lines = explode("\n", $data); + $header_line = trim($lines[0] ?? ''); + + + // Simple manual parsing for header detection + // This handles quoted CSV fields better than the library for detection + $fields = []; + $current_field = ''; + $in_quotes = false; + $quote_char = '"'; + + for ($i = 0; $i < strlen($header_line); $i++) { + $char = $header_line[$i]; + + if ($char === $quote_char && !$in_quotes) { + $in_quotes = true; + } elseif ($char === $quote_char && $in_quotes) { + // Check for escaped quote (double quote) + if ($i + 1 < strlen($header_line) && $header_line[$i + 1] === $quote_char) { + $current_field .= $quote_char; + $i++; // Skip next quote + } else { + $in_quotes = false; + } + } elseif ($char === $delimiter && !$in_quotes) { + $fields[] = trim($current_field); + $current_field = ''; + } else { + $current_field .= $char; + } + } + + // Add the last field + if ($current_field !== '') { + $fields[] = trim($current_field); + } + + // Clean up headers - remove quotes and trim whitespace + $headers = array_map(function ($header) { + return trim($header, '"\''); + }, $fields); + + + return array_values($headers); + } } diff --git a/src/Services/ImportExportSystem/BOMValidationService.php b/src/Services/ImportExportSystem/BOMValidationService.php new file mode 100644 index 00000000..74f81fe3 --- /dev/null +++ b/src/Services/ImportExportSystem/BOMValidationService.php @@ -0,0 +1,476 @@ +. + */ +namespace App\Services\ImportExportSystem; + +use App\Entity\Parts\Part; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * Service for validating BOM import data with comprehensive validation rules + * and user-friendly error messages. + */ +class BOMValidationService +{ + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly TranslatorInterface $translator + ) { + } + + /** + * Validation result structure + */ + public static function createValidationResult(): array + { + return [ + 'errors' => [], + 'warnings' => [], + 'info' => [], + 'is_valid' => true, + 'total_entries' => 0, + 'valid_entries' => 0, + 'invalid_entries' => 0, + ]; + } + + /** + * Validate a single BOM entry with comprehensive checks + */ + public function validateBOMEntry(array $mapped_entry, int $line_number, array $options = []): array + { + $result = [ + 'line_number' => $line_number, + 'errors' => [], + 'warnings' => [], + 'info' => [], + 'is_valid' => true, + ]; + + // Run all validation rules + $this->validateRequiredFields($mapped_entry, $result); + $this->validateDesignatorFormat($mapped_entry, $result); + $this->validateQuantityFormat($mapped_entry, $result); + $this->validateDesignatorQuantityMatch($mapped_entry, $result); + $this->validatePartDBLink($mapped_entry, $result); + $this->validateComponentName($mapped_entry, $result); + $this->validatePackageFormat($mapped_entry, $result); + $this->validateNumericFields($mapped_entry, $result); + + $result['is_valid'] = empty($result['errors']); + + return $result; + } + + /** + * Validate multiple BOM entries and provide summary + */ + public function validateBOMEntries(array $mapped_entries, array $options = []): array + { + $result = self::createValidationResult(); + $result['total_entries'] = count($mapped_entries); + + $line_results = []; + $all_errors = []; + $all_warnings = []; + $all_info = []; + + foreach ($mapped_entries as $index => $entry) { + $line_number = $index + 1; + $line_result = $this->validateBOMEntry($entry, $line_number, $options); + + $line_results[] = $line_result; + + if ($line_result['is_valid']) { + $result['valid_entries']++; + } else { + $result['invalid_entries']++; + } + + // Collect all messages + $all_errors = array_merge($all_errors, $line_result['errors']); + $all_warnings = array_merge($all_warnings, $line_result['warnings']); + $all_info = array_merge($all_info, $line_result['info']); + } + + // Add summary messages + $this->addSummaryMessages($result, $all_errors, $all_warnings, $all_info); + + $result['errors'] = $all_errors; + $result['warnings'] = $all_warnings; + $result['info'] = $all_info; + $result['line_results'] = $line_results; + $result['is_valid'] = empty($all_errors); + + return $result; + } + + /** + * Validate required fields are present + */ + private function validateRequiredFields(array $entry, array &$result): void + { + $required_fields = ['Designator', 'Quantity']; + + foreach ($required_fields as $field) { + if (!isset($entry[$field]) || trim($entry[$field]) === '') { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.required_field_missing', [ + '%line%' => $result['line_number'], + '%field%' => $field + ]); + } + } + } + + /** + * Validate designator format and content + */ + private function validateDesignatorFormat(array $entry, array &$result): void + { + if (!isset($entry['Designator']) || trim($entry['Designator']) === '') { + return; // Already handled by required fields validation + } + + $designator = trim($entry['Designator']); + $mountnames = array_map('trim', explode(',', $designator)); + + // Remove empty entries + $mountnames = array_filter($mountnames, fn($name) => !empty($name)); + + if (empty($mountnames)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.no_valid_designators', [ + '%line%' => $result['line_number'] + ]); + return; + } + + // Validate each mountname format (allow 1-2 uppercase letters, followed by 1+ digits) + $invalid_mountnames = []; + foreach ($mountnames as $mountname) { + if (!preg_match('/^[A-Z]{1,2}[0-9]+$/', $mountname)) { + $invalid_mountnames[] = $mountname; + } + } + + if (!empty($invalid_mountnames)) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.unusual_designator_format', [ + '%line%' => $result['line_number'], + '%designators%' => implode(', ', $invalid_mountnames) + ]); + } + + // Check for duplicate mountnames within the same line + $duplicates = array_diff_assoc($mountnames, array_unique($mountnames)); + if (!empty($duplicates)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.duplicate_designators', [ + '%line%' => $result['line_number'], + '%designators%' => implode(', ', array_unique($duplicates)) + ]); + } + } + + /** + * Validate quantity format and value + */ + private function validateQuantityFormat(array $entry, array &$result): void + { + if (!isset($entry['Quantity']) || trim($entry['Quantity']) === '') { + return; // Already handled by required fields validation + } + + $quantity_str = trim($entry['Quantity']); + + // Check if it's a valid number + if (!is_numeric($quantity_str)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.invalid_quantity', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str + ]); + return; + } + + $quantity = (float) $quantity_str; + + // Check for reasonable quantity values + if ($quantity <= 0) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.quantity_zero_or_negative', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str + ]); + } elseif ($quantity > 10000) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.quantity_unusually_high', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str + ]); + } + + // Check if quantity is a whole number when it should be + if (isset($entry['Designator'])) { + $designator = trim($entry['Designator']); + $mountnames = array_map('trim', explode(',', $designator)); + $mountnames = array_filter($mountnames, fn($name) => !empty($name)); + + if (count($mountnames) > 0 && $quantity != (int) $quantity) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.quantity_not_whole_number', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str, + '%count%' => count($mountnames) + ]); + } + } + } + + /** + * Validate that designator count matches quantity + */ + private function validateDesignatorQuantityMatch(array $entry, array &$result): void + { + if (!isset($entry['Designator']) || !isset($entry['Quantity'])) { + return; // Already handled by required fields validation + } + + $designator = trim($entry['Designator']); + $quantity_str = trim($entry['Quantity']); + + if (!is_numeric($quantity_str)) { + return; // Already handled by quantity validation + } + + $mountnames = array_map('trim', explode(',', $designator)); + $mountnames = array_filter($mountnames, fn($name) => !empty($name)); + $mountnames_count = count($mountnames); + $quantity = (float) $quantity_str; + + if ($mountnames_count !== (int) $quantity) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.quantity_designator_mismatch', [ + '%line%' => $result['line_number'], + '%quantity%' => $quantity_str, + '%count%' => $mountnames_count, + '%designators%' => $designator + ]); + } + } + + /** + * Validate Part-DB ID link + */ + private function validatePartDBLink(array $entry, array &$result): void + { + if (!isset($entry['Part-DB ID']) || trim($entry['Part-DB ID']) === '') { + return; + } + + $part_db_id = trim($entry['Part-DB ID']); + + if (!is_numeric($part_db_id)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.invalid_partdb_id', [ + '%line%' => $result['line_number'], + '%id%' => $part_db_id + ]); + return; + } + + $part_id = (int) $part_db_id; + + if ($part_id <= 0) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.partdb_id_zero_or_negative', [ + '%line%' => $result['line_number'], + '%id%' => $part_id + ]); + return; + } + + // Check if part exists in database + $existing_part = $this->entityManager->getRepository(Part::class)->find($part_id); + if (!$existing_part) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.partdb_id_not_found', [ + '%line%' => $result['line_number'], + '%id%' => $part_id + ]); + } else { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.partdb_link_success', [ + '%line%' => $result['line_number'], + '%name%' => $existing_part->getName(), + '%id%' => $part_id + ]); + } + } + + /** + * Validate component name/designation + */ + private function validateComponentName(array $entry, array &$result): void + { + $name_fields = ['MPN', 'Designation', 'Value']; + $has_name = false; + + foreach ($name_fields as $field) { + if (isset($entry[$field]) && trim($entry[$field]) !== '') { + $has_name = true; + break; + } + } + + if (!$has_name) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.no_component_name', [ + '%line%' => $result['line_number'] + ]); + } + } + + /** + * Validate package format + */ + private function validatePackageFormat(array $entry, array &$result): void + { + if (!isset($entry['Package']) || trim($entry['Package']) === '') { + return; + } + + $package = trim($entry['Package']); + + // Check for common package format issues + if (strlen($package) > 100) { + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.package_name_too_long', [ + '%line%' => $result['line_number'], + '%package%' => $package + ]); + } + + // Check for library prefixes (KiCad format) + if (str_contains($package, ':')) { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.library_prefix_detected', [ + '%line%' => $result['line_number'], + '%package%' => $package + ]); + } + } + + /** + * Validate numeric fields + */ + private function validateNumericFields(array $entry, array &$result): void + { + $numeric_fields = ['Quantity', 'Part-DB ID']; + + foreach ($numeric_fields as $field) { + if (isset($entry[$field]) && trim($entry[$field]) !== '') { + $value = trim($entry[$field]); + if (!is_numeric($value)) { + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.non_numeric_field', [ + '%line%' => $result['line_number'], + '%field%' => $field, + '%value%' => $value + ]); + } + } + } + } + + /** + * Add summary messages to validation result + */ + private function addSummaryMessages(array &$result, array $errors, array $warnings, array $info): void + { + $total_entries = $result['total_entries']; + $valid_entries = $result['valid_entries']; + $invalid_entries = $result['invalid_entries']; + + // Add summary info + if ($total_entries > 0) { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.import_summary', [ + '%total%' => $total_entries, + '%valid%' => $valid_entries, + '%invalid%' => $invalid_entries + ]); + } + + // Add error summary + if (!empty($errors)) { + $error_count = count($errors); + $result['errors'][] = $this->translator->trans('project.bom_import.validation.errors.summary', [ + '%count%' => $error_count + ]); + } + + // Add warning summary + if (!empty($warnings)) { + $warning_count = count($warnings); + $result['warnings'][] = $this->translator->trans('project.bom_import.validation.warnings.summary', [ + '%count%' => $warning_count + ]); + } + + // Add success message if all entries are valid + if ($total_entries > 0 && $invalid_entries === 0) { + $result['info'][] = $this->translator->trans('project.bom_import.validation.info.all_valid'); + } + } + + /** + * Get user-friendly error message for a validation result + */ + public function getErrorMessage(array $validation_result): string + { + if ($validation_result['is_valid']) { + return ''; + } + + $messages = []; + + if (!empty($validation_result['errors'])) { + $messages[] = 'Errors:'; + foreach ($validation_result['errors'] as $error) { + $messages[] = '• ' . $error; + } + } + + if (!empty($validation_result['warnings'])) { + $messages[] = 'Warnings:'; + foreach ($validation_result['warnings'] as $warning) { + $messages[] = '• ' . $warning; + } + } + + return implode("\n", $messages); + } + + /** + * Get validation statistics + */ + public function getValidationStats(array $validation_result): array + { + return [ + 'total_entries' => $validation_result['total_entries'] ?? 0, + 'valid_entries' => $validation_result['valid_entries'] ?? 0, + 'invalid_entries' => $validation_result['invalid_entries'] ?? 0, + 'error_count' => count($validation_result['errors'] ?? []), + 'warning_count' => count($validation_result['warnings'] ?? []), + 'info_count' => count($validation_result['info'] ?? []), + 'success_rate' => $validation_result['total_entries'] > 0 + ? round(($validation_result['valid_entries'] / $validation_result['total_entries']) * 100, 1) + : 0, + ]; + } +} \ No newline at end of file diff --git a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php index 58df3b82..2d83fc7c 100755 --- a/src/Services/InfoProviderSystem/Providers/LCSCProvider.php +++ b/src/Services/InfoProviderSystem/Providers/LCSCProvider.php @@ -123,11 +123,11 @@ class LCSCProvider implements InfoProviderInterface */ private function queryByTerm(string $term): array { - $response = $this->lcscClient->request('GET', self::ENDPOINT_URL . "/search/global", [ + $response = $this->lcscClient->request('POST', self::ENDPOINT_URL . "/search/v2/global", [ 'headers' => [ 'Cookie' => new Cookie('currencyCode', $this->settings->currency) ], - 'query' => [ + 'json' => [ 'keyword' => $term, ], ]); @@ -165,6 +165,9 @@ class LCSCProvider implements InfoProviderInterface if ($field === null) { return null; } + // Replace "range" indicators with mathematical tilde symbols + // so they don't get rendered as strikethrough by Markdown + $field = preg_replace("/~/", "\u{223c}", $field); return strip_tags($field); } @@ -197,9 +200,6 @@ class LCSCProvider implements InfoProviderInterface $category = $product['parentCatalogName'] ?? null; if (isset($product['catalogName'])) { $category = ($category ?? '') . ' -> ' . $product['catalogName']; - - // Replace the / with a -> for better readability - $category = str_replace('/', ' -> ', $category); } return new PartDetailDTO( diff --git a/src/Services/InfoProviderSystem/Providers/PollinProvider.php b/src/Services/InfoProviderSystem/Providers/PollinProvider.php index 55fa335a..b74e0365 100644 --- a/src/Services/InfoProviderSystem/Providers/PollinProvider.php +++ b/src/Services/InfoProviderSystem/Providers/PollinProvider.php @@ -158,7 +158,8 @@ class PollinProvider implements InfoProviderInterface category: $this->parseCategory($dom), manufacturer: $dom->filter('meta[property="product:brand"]')->count() > 0 ? $dom->filter('meta[property="product:brand"]')->attr('content') : null, preview_image_url: $dom->filter('meta[property="og:image"]')->attr('content'), - manufacturing_status: $this->mapAvailability($dom->filter('link[itemprop="availability"]')->attr('href')), + //TODO: Find another way to determine the manufacturing status, as the itemprop="availability" is often is not existing anymore in the page + //manufacturing_status: $this->mapAvailability($dom->filter('link[itemprop="availability"]')->attr('href')), provider_url: $productPageUrl, notes: $this->parseNotes($dom), datasheets: $this->parseDatasheets($dom), diff --git a/src/Services/Tools/ExchangeRateUpdater.php b/src/Services/Tools/ExchangeRateUpdater.php index 7c14b16f..6eb7ec13 100644 --- a/src/Services/Tools/ExchangeRateUpdater.php +++ b/src/Services/Tools/ExchangeRateUpdater.php @@ -26,6 +26,8 @@ use App\Entity\PriceInformations\Currency; use App\Settings\SystemSettings\LocalizationSettings; use Brick\Math\BigDecimal; use Brick\Math\RoundingMode; +use Exchanger\Exception\UnsupportedCurrencyPairException; +use Exchanger\Exception\UnsupportedExchangeQueryException; use Swap\Swap; class ExchangeRateUpdater @@ -39,15 +41,21 @@ class ExchangeRateUpdater */ public function update(Currency $currency): Currency { - //Currency pairs are always in the format "BASE/QUOTE" - $rate = $this->swap->latest($this->localizationSettings->baseCurrency.'/'.$currency->getIsoCode()); - //The rate says how many quote units are worth one base unit - //So we need to invert it to get the exchange rate + try { + //Try it in the direction QUOTE/BASE first, as most providers provide rates in this direction + $rate = $this->swap->latest($currency->getIsoCode().'/'.$this->localizationSettings->baseCurrency); + $effective_rate = BigDecimal::of($rate->getValue()); + } catch (UnsupportedCurrencyPairException|UnsupportedExchangeQueryException $exception) { + //Otherwise try to get it inverse and calculate it ourselfes, from the format "BASE/QUOTE" + $rate = $this->swap->latest($this->localizationSettings->baseCurrency.'/'.$currency->getIsoCode()); + //The rate says how many quote units are worth one base unit + //So we need to invert it to get the exchange rate - $rate_bd = BigDecimal::of($rate->getValue()); - $rate_inverted = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + $rate_bd = BigDecimal::of($rate->getValue()); + $effective_rate = BigDecimal::one()->dividedBy($rate_bd, Currency::PRICE_SCALE, RoundingMode::HALF_UP); + } - $currency->setExchangeRate($rate_inverted); + $currency->setExchangeRate($effective_rate); return $currency; } diff --git a/src/Services/UserSystem/VoterHelper.php b/src/Services/UserSystem/VoterHelper.php index 644351f4..d3c5368c 100644 --- a/src/Services/UserSystem/VoterHelper.php +++ b/src/Services/UserSystem/VoterHelper.php @@ -28,6 +28,9 @@ use App\Repository\UserRepository; use App\Security\ApiTokenAuthenticatedToken; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Vote; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Contracts\Translation\TranslatorInterface; /** * @see \App\Tests\Services\UserSystem\VoterHelperTest @@ -35,10 +38,14 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; final class VoterHelper { private readonly UserRepository $userRepository; + private readonly array $permissionStructure; - public function __construct(private readonly PermissionManager $permissionManager, private readonly EntityManagerInterface $entityManager) + public function __construct(private readonly PermissionManager $permissionManager, + private readonly TranslatorInterface $translator, + private readonly EntityManagerInterface $entityManager) { $this->userRepository = $this->entityManager->getRepository(User::class); + $this->permissionStructure = $this->permissionManager->getPermissionStructure(); } /** @@ -47,11 +54,16 @@ final class VoterHelper * @param TokenInterface $token The token to check * @param string $permission The permission to check * @param string $operation The operation to check + * @param Vote|null $vote The vote object to add reasons to (optional). If null, no reasons are added. * @return bool */ - public function isGranted(TokenInterface $token, string $permission, string $operation): bool + public function isGranted(TokenInterface $token, string $permission, string $operation, ?Vote $vote = null): bool { - return $this->isGrantedTrinary($token, $permission, $operation) ?? false; + $tmp = $this->isGrantedTrinary($token, $permission, $operation) ?? false; + if ($tmp === false) { + $this->addReason($vote, $permission, $operation); + } + return $tmp; } /** @@ -124,4 +136,17 @@ final class VoterHelper { return $this->permissionManager->isValidOperation($permission, $operation); } -} \ No newline at end of file + + public function addReason(?Vote $voter, string $permission, $operation): void + { + if ($voter !== null) { + $voter->addReason(sprintf("User does not have permission %s -> %s -> %s (%s.%s).", + $this->translator->trans('perm.group.'.($this->permissionStructure['perms'][$permission]['group'] ?? 'unknown') ), + $this->translator->trans($this->permissionStructure['perms'][$permission]['label'] ?? $permission), + $this->translator->trans($this->permissionStructure['perms'][$permission]['operations'][$operation]['label'] ?? $operation), + $permission, + $operation + )); + } + } +} diff --git a/symfony.lock b/symfony.lock index d301c269..7c136b4b 100644 --- a/symfony.lock +++ b/symfony.lock @@ -133,15 +133,6 @@ "ekino/phpstan-banned-code": { "version": "v0.3.1" }, - "florianv/exchanger": { - "version": "1.4.1" - }, - "florianv/swap": { - "version": "3.5.0" - }, - "florianv/swap-bundle": { - "version": "5.0.x-dev" - }, "gregwar/captcha": { "version": "v1.1.7" }, @@ -254,6 +245,9 @@ "./config/packages/datatables.yaml" ] }, + "part-db/swap-bundle": { + "version": "v6.0.0" + }, "php-http/discovery": { "version": "1.18", "recipe": { diff --git a/templates/bundles/TwigBundle/Exception/error403.html.twig b/templates/bundles/TwigBundle/Exception/error403.html.twig index f5987179..334670fc 100644 --- a/templates/bundles/TwigBundle/Exception/error403.html.twig +++ b/templates/bundles/TwigBundle/Exception/error403.html.twig @@ -1,6 +1,9 @@ {% extends "bundles/TwigBundle/Exception/error.html.twig" %} {% block status_comment %} - Nice try! But you are not allowed to do this! + Nice try! But you are not allowed to do this!
+ {{ exception.message }}
If you think you should have access to this ressource, contact the adminstrator. -{% endblock %} \ No newline at end of file + + +{% endblock %} diff --git a/templates/info_providers/providers.macro.html.twig b/templates/info_providers/providers.macro.html.twig index 827a95fd..bf530ebd 100644 --- a/templates/info_providers/providers.macro.html.twig +++ b/templates/info_providers/providers.macro.html.twig @@ -23,7 +23,7 @@
{% if provider.providerInfo.settings_class is defined %} - {% endif %} diff --git a/templates/label_system/dialog.html.twig b/templates/label_system/dialog.html.twig index 50db99e7..037b549e 100644 --- a/templates/label_system/dialog.html.twig +++ b/templates/label_system/dialog.html.twig @@ -100,6 +100,10 @@
{% endif %} + {% if form.update_profile is defined %} + {{ form_row(form.update_profile) }} + {% endif %} +
@@ -133,4 +137,4 @@
{% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/projects/_bom_validation_results.html.twig b/templates/projects/_bom_validation_results.html.twig new file mode 100644 index 00000000..68f1b827 --- /dev/null +++ b/templates/projects/_bom_validation_results.html.twig @@ -0,0 +1,186 @@ +{# BOM Validation Results Component #} +{# + Usage: + {% include 'projects/_bom_validation_results.html.twig' with { + validation_result: validation_result, + show_summary: true, + show_details: true + } %} +#} + +{% if validation_result is defined and validation_result is not empty %} + {% set stats = validation_result %} + + {# Validation Summary #} + {% if show_summary is defined and show_summary %} +
+
+
+
+
+ + {% trans %}project.bom_import.validation.summary{% endtrans %} +
+
+
+
+
+
+
{{ stats.total_entries }}
+ {% trans %}project.bom_import.validation.total_entries{% endtrans %} +
+
+
+
+
{{ stats.valid_entries }}
+ {% trans %}project.bom_import.validation.valid_entries{% endtrans %} +
+
+
+
+
{{ stats.invalid_entries }}
+ {% trans %}project.bom_import.validation.invalid_entries{% endtrans %} +
+
+
+
+
+ {% if stats.total_entries > 0 %} + {{ ((stats.valid_entries / stats.total_entries) * 100) | round(1) }}% + {% else %} + 0% + {% endif %} +
+ {% trans %}project.bom_import.validation.success_rate{% endtrans %} +
+
+
+
+
+
+
+ {% endif %} + + {# Validation Messages #} + {% if validation_result.errors is defined and validation_result.errors is not empty %} +
+

{% trans %}project.bom_import.validation.errors.title{% endtrans %}

+

{% trans %}project.bom_import.validation.errors.description{% endtrans %}

+
    + {% for error in validation_result.errors %} +
  • {{ error|raw }}
  • + {% endfor %} +
+
+ {% endif %} + + {% if validation_result.warnings is defined and validation_result.warnings is not empty %} +
+

{% trans %}project.bom_import.validation.warnings.title{% endtrans %}

+

{% trans %}project.bom_import.validation.warnings.description{% endtrans %}

+
    + {% for warning in validation_result.warnings %} +
  • {{ warning|raw }}
  • + {% endfor %} +
+
+ {% endif %} + + {% if validation_result.info is defined and validation_result.info is not empty %} +
+

{% trans %}project.bom_import.validation.info.title{% endtrans %}

+
    + {% for info in validation_result.info %} +
  • {{ info|raw }}
  • + {% endfor %} +
+
+ {% endif %} + + {# Detailed Line-by-Line Results #} + {% if show_details is defined and show_details and validation_result.line_results is defined %} +
+
+
+ + {% trans %}project.bom_import.validation.details.title{% endtrans %} +
+
+
+
+ + + + + + + + + + {% for line_result in validation_result.line_results %} + + + + + + {% endfor %} + +
{% trans %}project.bom_import.validation.details.line{% endtrans %}{% trans %}project.bom_import.validation.details.status{% endtrans %}{% trans %}project.bom_import.validation.details.messages{% endtrans %}
+ {{ line_result.line_number }} + + {% if line_result.is_valid %} + + + {% trans %}project.bom_import.validation.details.valid{% endtrans %} + + {% else %} + + + {% trans %}project.bom_import.validation.details.invalid{% endtrans %} + + {% endif %} + + {% if line_result.errors is not empty %} +
+ {% for error in line_result.errors %} +
{{ error|raw }}
+ {% endfor %} +
+ {% endif %} + {% if line_result.warnings is not empty %} +
+ {% for warning in line_result.warnings %} +
{{ warning|raw }}
+ {% endfor %} +
+ {% endif %} + {% if line_result.info is not empty %} +
+ {% for info in line_result.info %} +
{{ info|raw }}
+ {% endfor %} +
+ {% endif %} +
+
+
+
+ {% endif %} + + {# Action Buttons #} + {% if validation_result.is_valid is defined %} +
+ {% if validation_result.is_valid %} +
+ + {% trans %}project.bom_import.validation.all_valid{% endtrans %} +
+ {% else %} +
+ + {% trans %}project.bom_import.validation.fix_errors{% endtrans %} +
+ {% endif %} +
+ {% endif %} +{% endif %} \ No newline at end of file diff --git a/templates/projects/import_bom_map_fields.html.twig b/templates/projects/import_bom_map_fields.html.twig new file mode 100644 index 00000000..4e45eb08 --- /dev/null +++ b/templates/projects/import_bom_map_fields.html.twig @@ -0,0 +1,204 @@ +{% extends "main_card.html.twig" %} + +{% block title %}{% trans %}project.bom_import.map_fields{% endtrans %}{% endblock %} + +{% block card_title %} + + {% trans %}project.bom_import.map_fields{% endtrans %}{% if project %}: {{ project.name }}{% endif %} +{% endblock %} + +{% block card_content %} + {% if validation_result is defined %} + {% include 'projects/_bom_validation_results.html.twig' with { + validation_result: validation_result, + show_summary: true, + show_details: false + } %} + {% endif %} + +
+
+
+ + {% trans %}project.bom_import.map_fields.help{% endtrans %} +
+
+ + {% trans %}project.bom_import.field_mapping.priority_note{% endtrans %} +
+
+
+ + {{ form_start(form) }} + +
+
+ {{ form_row(form.delimiter) }} +
+
+ +
+
+
+ + {% trans %}project.bom_import.field_mapping.title{% endtrans %} +
+
+
+
+ + + + + + + + + + + {% for field in detected_fields %} + + + + + + + {% endfor %} + +
{% trans %}project.bom_import.field_mapping.csv_field{% endtrans %}{% trans %}project.bom_import.field_mapping.maps_to{% endtrans %}{% trans %}project.bom_import.field_mapping.suggestion{% endtrans %}{% trans %}project.bom_import.field_mapping.priority{% endtrans %}
+ {{ field }} + + {{ form_widget(form['mapping_' ~ field_name_mapping[field]], { + 'attr': { + 'class': 'form-select field-mapping-select', + 'data-field': field + } + }) }} + + {% if suggested_mapping[field] is defined %} + + + {{ suggested_mapping[field] }} + + {% else %} + + + {% trans %}project.bom_import.field_mapping.no_suggestion{% endtrans %} + + {% endif %} + + +
+
+ +
+
{% trans %}project.bom_import.field_mapping.summary{% endtrans %}:
+
+ + {% trans %}project.bom_import.field_mapping.select_to_see_summary{% endtrans %} +
+
+
+
+ +
+ {{ form_widget(form.submit, { + 'attr': { + 'class': 'btn btn-primary' + } + }) }} + + + {% trans %}common.back{% endtrans %} + +
+ + {{ form_end(form) }} + + +{% endblock %} diff --git a/tests/API/Endpoints/CurrencyEndpointTest.php b/tests/API/Endpoints/CurrencyEndpointTest.php index 78434ea3..a463daeb 100644 --- a/tests/API/Endpoints/CurrencyEndpointTest.php +++ b/tests/API/Endpoints/CurrencyEndpointTest.php @@ -36,7 +36,7 @@ class CurrencyEndpointTest extends CrudEndpointTestCase { $this->_testGetCollection(); self::assertJsonContains([ - 'hydra:totalItems' => 0, + 'hydra:totalItems' => 4, //The 4 currencies from our fixtures ]); } @@ -45,7 +45,7 @@ class CurrencyEndpointTest extends CrudEndpointTestCase { $this->_testPostItem([ 'name' => 'Test API', - 'iso_code' => 'USD', + 'iso_code' => 'CAD', ]); } @@ -61,4 +61,4 @@ class CurrencyEndpointTest extends CrudEndpointTestCase { $this->_testDeleteItem(5); }*/ -} \ No newline at end of file +} diff --git a/tests/Controller/AdminPages/CurrencyController.php b/tests/Controller/AdminPages/CurrencyController.php new file mode 100644 index 00000000..21f94a29 --- /dev/null +++ b/tests/Controller/AdminPages/CurrencyController.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + +namespace App\Tests\Controller\AdminPages; + +use App\Entity\PriceInformations\Currency; +use PHPUnit\Framework\Attributes\Group; +use App\Entity\Parts\Manufacturer; + +#[Group('slow')] +#[Group('DB')] +class CurrencyController extends AbstractAdminController +{ + protected static string $base_path = '/en/currency'; + protected static string $entity_class = Currency::class; +} diff --git a/tests/Services/ImportExportSystem/BOMImporterTest.php b/tests/Services/ImportExportSystem/BOMImporterTest.php index b9aba1d4..52c633d0 100644 --- a/tests/Services/ImportExportSystem/BOMImporterTest.php +++ b/tests/Services/ImportExportSystem/BOMImporterTest.php @@ -22,9 +22,12 @@ declare(strict_types=1); */ namespace App\Tests\Services\ImportExportSystem; +use App\Entity\Parts\Part; +use App\Entity\Parts\Supplier; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Services\ImportExportSystem\BOMImporter; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\File\File; @@ -36,11 +39,17 @@ class BOMImporterTest extends WebTestCase */ protected $service; + /** + * @var EntityManagerInterface + */ + protected $entityManager; + protected function setUp(): void { //Get a service instance. self::bootKernel(); $this->service = self::getContainer()->get(BOMImporter::class); + $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); } public function testImportFileIntoProject(): void @@ -119,4 +128,489 @@ class BOMImporterTest extends WebTestCase $this->service->stringToBOMEntries($input, ['type' => 'kicad_pcbnew']); } + + public function testDetectFields(): void + { + $input = <<service->detectFields($input); + + $this->assertIsArray($fields); + $this->assertCount(8, $fields); + $this->assertContains('Reference', $fields); + $this->assertContains('Value', $fields); + $this->assertContains('Footprint', $fields); + $this->assertContains('Quantity', $fields); + $this->assertContains('MPN', $fields); + $this->assertContains('Manufacturer', $fields); + $this->assertContains('LCSC SPN', $fields); + $this->assertContains('Mouser SPN', $fields); + } + + public function testDetectFieldsWithQuotes(): void + { + $input = <<service->detectFields($input); + + $this->assertIsArray($fields); + $this->assertCount(8, $fields); + $this->assertEquals('Reference', $fields[0]); + $this->assertEquals('Value', $fields[1]); + } + + public function testDetectFieldsWithSemicolon(): void + { + $input = <<service->detectFields($input, ';'); + + $this->assertIsArray($fields); + $this->assertCount(8, $fields); + $this->assertEquals('Reference', $fields[0]); + $this->assertEquals('Value', $fields[1]); + } + + public function testGetAvailableFieldTargets(): void + { + $targets = $this->service->getAvailableFieldTargets(); + + $this->assertIsArray($targets); + $this->assertArrayHasKey('Designator', $targets); + $this->assertArrayHasKey('Quantity', $targets); + $this->assertArrayHasKey('Value', $targets); + $this->assertArrayHasKey('Package', $targets); + $this->assertArrayHasKey('MPN', $targets); + $this->assertArrayHasKey('Manufacturer', $targets); + $this->assertArrayHasKey('Part-DB ID', $targets); + $this->assertArrayHasKey('Comment', $targets); + + // Check structure of a target + $this->assertArrayHasKey('label', $targets['Designator']); + $this->assertArrayHasKey('description', $targets['Designator']); + $this->assertArrayHasKey('required', $targets['Designator']); + $this->assertArrayHasKey('multiple', $targets['Designator']); + + $this->assertTrue($targets['Designator']['required']); + $this->assertTrue($targets['Quantity']['required']); + $this->assertFalse($targets['Value']['required']); + } + + public function testGetAvailableFieldTargetsWithSuppliers(): void + { + // Create test suppliers + $supplier1 = new Supplier(); + $supplier1->setName('LCSC'); + $supplier2 = new Supplier(); + $supplier2->setName('Mouser'); + + $this->entityManager->persist($supplier1); + $this->entityManager->persist($supplier2); + $this->entityManager->flush(); + + $targets = $this->service->getAvailableFieldTargets(); + + $this->assertArrayHasKey('LCSC SPN', $targets); + $this->assertArrayHasKey('Mouser SPN', $targets); + + $this->assertEquals('LCSC SPN', $targets['LCSC SPN']['label']); + $this->assertEquals('Mouser SPN', $targets['Mouser SPN']['label']); + $this->assertFalse($targets['LCSC SPN']['required']); + $this->assertTrue($targets['LCSC SPN']['multiple']); + + // Clean up + $this->entityManager->remove($supplier1); + $this->entityManager->remove($supplier2); + $this->entityManager->flush(); + } + + public function testGetSuggestedFieldMapping(): void + { + $detected_fields = [ + 'Reference', + 'Value', + 'Footprint', + 'Quantity', + 'MPN', + 'Manufacturer', + 'LCSC', + 'Mouser', + 'Part-DB ID', + 'Comment' + ]; + + $suggestions = $this->service->getSuggestedFieldMapping($detected_fields); + + $this->assertIsArray($suggestions); + $this->assertEquals('Designator', $suggestions['Reference']); + $this->assertEquals('Value', $suggestions['Value']); + $this->assertEquals('Package', $suggestions['Footprint']); + $this->assertEquals('Quantity', $suggestions['Quantity']); + $this->assertEquals('MPN', $suggestions['MPN']); + $this->assertEquals('Manufacturer', $suggestions['Manufacturer']); + $this->assertEquals('Part-DB ID', $suggestions['Part-DB ID']); + $this->assertEquals('Comment', $suggestions['Comment']); + } + + public function testGetSuggestedFieldMappingWithSuppliers(): void + { + // Create test suppliers + $supplier1 = new Supplier(); + $supplier1->setName('LCSC'); + $supplier2 = new Supplier(); + $supplier2->setName('Mouser'); + + $this->entityManager->persist($supplier1); + $this->entityManager->persist($supplier2); + $this->entityManager->flush(); + + $detected_fields = [ + 'Reference', + 'LCSC', + 'Mouser', + 'lcsc_part', + 'mouser_spn' + ]; + + $suggestions = $this->service->getSuggestedFieldMapping($detected_fields); + + $this->assertIsArray($suggestions); + $this->assertEquals('Designator', $suggestions['Reference']); + // Note: The exact mapping depends on the pattern matching logic + // We just check that supplier fields are mapped to something + $this->assertArrayHasKey('LCSC', $suggestions); + $this->assertArrayHasKey('Mouser', $suggestions); + $this->assertArrayHasKey('lcsc_part', $suggestions); + $this->assertArrayHasKey('mouser_spn', $suggestions); + + // Clean up + $this->entityManager->remove($supplier1); + $this->entityManager->remove($supplier2); + $this->entityManager->flush(); + } + + public function testValidateFieldMappingValid(): void + { + $field_mapping = [ + 'Reference' => 'Designator', + 'Quantity' => 'Quantity', + 'Value' => 'Value' + ]; + + $detected_fields = ['Reference', 'Quantity', 'Value', 'MPN']; + + $result = $this->service->validateFieldMapping($field_mapping, $detected_fields); + + $this->assertIsArray($result); + $this->assertArrayHasKey('errors', $result); + $this->assertArrayHasKey('warnings', $result); + $this->assertArrayHasKey('is_valid', $result); + + $this->assertTrue($result['is_valid']); + $this->assertEmpty($result['errors']); + $this->assertNotEmpty($result['warnings']); // Should warn about unmapped MPN + } + + public function testValidateFieldMappingMissingRequired(): void + { + $field_mapping = [ + 'Value' => 'Value', + 'MPN' => 'MPN' + ]; + + $detected_fields = ['Value', 'MPN']; + + $result = $this->service->validateFieldMapping($field_mapping, $detected_fields); + + $this->assertFalse($result['is_valid']); + $this->assertNotEmpty($result['errors']); + $this->assertContains("Required field 'Designator' is not mapped from any CSV column.", $result['errors']); + $this->assertContains("Required field 'Quantity' is not mapped from any CSV column.", $result['errors']); + } + + public function testValidateFieldMappingInvalidTarget(): void + { + $field_mapping = [ + 'Reference' => 'Designator', + 'Quantity' => 'Quantity', + 'Value' => 'InvalidTarget' + ]; + + $detected_fields = ['Reference', 'Quantity', 'Value']; + + $result = $this->service->validateFieldMapping($field_mapping, $detected_fields); + + $this->assertFalse($result['is_valid']); + $this->assertNotEmpty($result['errors']); + $this->assertContains("Invalid target field 'InvalidTarget' for CSV field 'Value'.", $result['errors']); + } + + public function testStringToBOMEntriesKiCADSchematic(): void + { + $input = << 'Designator', + 'Value' => 'Value', + 'Footprint' => 'Package', + 'Quantity' => 'Quantity', + 'MPN' => 'MPN', + 'Manufacturer' => 'Manufacturer', + 'LCSC SPN' => 'LCSC SPN', + 'Mouser SPN' => 'Mouser SPN' + ]; + + $bom_entries = $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + + $this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries); + $this->assertCount(2, $bom_entries); + + // Check first entry + $this->assertEquals('R1,R2', $bom_entries[0]->getMountnames()); + $this->assertEquals(2.0, $bom_entries[0]->getQuantity()); + $this->assertEquals('CRCW080510K0FKEA (R_0805_2012Metric)', $bom_entries[0]->getName()); + $this->assertStringContainsString('Value: 10k', $bom_entries[0]->getComment()); + $this->assertStringContainsString('MPN: CRCW080510K0FKEA', $bom_entries[0]->getComment()); + $this->assertStringContainsString('Manf: Vishay', $bom_entries[0]->getComment()); + + // Check second entry + $this->assertEquals('C1', $bom_entries[1]->getMountnames()); + $this->assertEquals(1.0, $bom_entries[1]->getQuantity()); + } + + public function testStringToBOMEntriesKiCADSchematicWithPriority(): void + { + $input = << 'Designator', + 'Value' => 'Value', + 'MPN1' => 'MPN', + 'MPN2' => 'MPN', + 'Quantity' => 'Quantity' + ]; + + $field_priorities = [ + 'MPN1' => 1, + 'MPN2' => 2 + ]; + + $bom_entries = $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'field_priorities' => $field_priorities, + 'delimiter' => ',' + ]); + + $this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries); + $this->assertCount(2, $bom_entries); + + // First entry should use MPN1 (higher priority) + $this->assertEquals('CRCW080510K0FKEA', $bom_entries[0]->getName()); + + // Second entry should use MPN2 (MPN1 is empty) + $this->assertEquals('CL21A104KOCLRNC', $bom_entries[1]->getName()); + } + + public function testStringToBOMEntriesKiCADSchematicWithPartDBID(): void + { + // Create a test part with required fields + $part = new Part(); + $part->setName('Test Part'); + $part->setCategory($this->getDefaultCategory($this->entityManager)); + $this->entityManager->persist($part); + $this->entityManager->flush(); + + $input = <<getID()}","2" + CSV; + + $field_mapping = [ + 'Reference' => 'Designator', + 'Value' => 'Value', + 'Part-DB ID' => 'Part-DB ID', + 'Quantity' => 'Quantity' + ]; + + $bom_entries = $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + + $this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries); + $this->assertCount(1, $bom_entries); + + $this->assertEquals('Test Part', $bom_entries[0]->getName()); + $this->assertSame($part, $bom_entries[0]->getPart()); + $this->assertStringContainsString("Part-DB ID: {$part->getID()}", $bom_entries[0]->getComment()); + + // Clean up + $this->entityManager->remove($part); + $this->entityManager->flush(); + } + + public function testStringToBOMEntriesKiCADSchematicWithInvalidPartDBID(): void + { + $input = << 'Designator', + 'Value' => 'Value', + 'Part-DB ID' => 'Part-DB ID', + 'Quantity' => 'Quantity' + ]; + + $bom_entries = $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + + $this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries); + $this->assertCount(1, $bom_entries); + + $this->assertEquals('10k', $bom_entries[0]->getName()); // Should use Value as name + $this->assertNull($bom_entries[0]->getPart()); // Should not link to part + $this->assertStringContainsString("Part-DB ID: 99999 (NOT FOUND)", $bom_entries[0]->getComment()); + } + + public function testStringToBOMEntriesKiCADSchematicMergeDuplicates(): void + { + $input = << 'Designator', + 'Value' => 'Value', + 'MPN' => 'MPN', + 'Quantity' => 'Quantity' + ]; + + $bom_entries = $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + + $this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries); + $this->assertCount(1, $bom_entries); // Should merge into one entry + + $this->assertEquals('R1,R2', $bom_entries[0]->getMountnames()); + $this->assertEquals(2.0, $bom_entries[0]->getQuantity()); + $this->assertEquals('CRCW080510K0FKEA', $bom_entries[0]->getName()); + } + + public function testStringToBOMEntriesKiCADSchematicMissingRequired(): void + { + $input = << 'Value', + 'MPN' => 'MPN' + ]; + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Required field "Designator" is missing or empty'); + + $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + } + + public function testStringToBOMEntriesKiCADSchematicQuantityMismatch(): void + { + $input = << 'Designator', + 'Value' => 'Value', + 'Quantity' => 'Quantity' + ]; + + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Mismatch between quantity and component references'); + + $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + } + + public function testStringToBOMEntriesKiCADSchematicWithBOM(): void + { + // Test with BOM (Byte Order Mark) + $input = "\xEF\xBB\xBF" . << 'Designator', + 'Value' => 'Value', + 'Quantity' => 'Quantity' + ]; + + $bom_entries = $this->service->stringToBOMEntries($input, [ + 'type' => 'kicad_schematic', + 'field_mapping' => $field_mapping, + 'delimiter' => ',' + ]); + + $this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries); + $this->assertCount(1, $bom_entries); + $this->assertEquals('R1,R2', $bom_entries[0]->getMountnames()); + } + + private function getDefaultCategory(EntityManagerInterface $entityManager) + { + // Get the first available category or create a default one + $categoryRepo = $entityManager->getRepository(\App\Entity\Parts\Category::class); + $categories = $categoryRepo->findAll(); + + if (empty($categories)) { + // Create a default category if none exists + $category = new \App\Entity\Parts\Category(); + $category->setName('Default Category'); + $entityManager->persist($category); + $entityManager->flush(); + return $category; + } + + return $categories[0]; + } } diff --git a/tests/Services/ImportExportSystem/BOMValidationServiceTest.php b/tests/Services/ImportExportSystem/BOMValidationServiceTest.php new file mode 100644 index 00000000..055db8b4 --- /dev/null +++ b/tests/Services/ImportExportSystem/BOMValidationServiceTest.php @@ -0,0 +1,349 @@ +. + */ +namespace App\Tests\Services\ImportExportSystem; + +use App\Entity\Parts\Part; +use App\Services\ImportExportSystem\BOMValidationService; +use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @see \App\Services\ImportExportSystem\BOMValidationService + */ +class BOMValidationServiceTest extends WebTestCase +{ + private BOMValidationService $validationService; + private EntityManagerInterface $entityManager; + private TranslatorInterface $translator; + + protected function setUp(): void + { + self::bootKernel(); + $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); + $this->translator = self::getContainer()->get(TranslatorInterface::class); + $this->validationService = new BOMValidationService($this->entityManager, $this->translator); + } + + public function testValidateBOMEntryWithValidData(): void + { + $entry = [ + 'Designator' => 'R1,C2,R3', + 'Quantity' => '3', + 'MPN' => 'RES-10K', + 'Package' => '0603', + 'Value' => '10k', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertTrue($result['is_valid']); + $this->assertEmpty($result['errors']); + $this->assertEquals(1, $result['line_number']); + } + + public function testValidateBOMEntryWithMissingRequiredFields(): void + { + $entry = [ + 'MPN' => 'RES-10K', + 'Package' => '0603', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertCount(2, $result['errors']); + $this->assertStringContainsString('Designator', (string) $result['errors'][0]); + $this->assertStringContainsString('Quantity', (string) $result['errors'][1]); + } + + public function testValidateBOMEntryWithQuantityMismatch(): void + { + $entry = [ + 'Designator' => 'R1,C2,R3,C4', + 'Quantity' => '3', + 'MPN' => 'RES-10K', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertCount(1, $result['errors']); + $this->assertStringContainsString('Mismatch between quantity and component references', (string) $result['errors'][0]); + } + + public function testValidateBOMEntryWithInvalidQuantity(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => 'abc', + 'MPN' => 'RES-10K', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertGreaterThanOrEqual(1, count($result['errors'])); + $this->assertStringContainsString('not a valid number', implode(' ', array_map('strval', $result['errors']))); + } + + public function testValidateBOMEntryWithZeroQuantity(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => '0', + 'MPN' => 'RES-10K', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertGreaterThanOrEqual(1, count($result['errors'])); + $this->assertStringContainsString('must be greater than 0', implode(' ', array_map('strval', $result['errors']))); + } + + public function testValidateBOMEntryWithDuplicateDesignators(): void + { + $entry = [ + 'Designator' => 'R1,R1,C2', + 'Quantity' => '3', + 'MPN' => 'RES-10K', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertCount(1, $result['errors']); + $this->assertStringContainsString('Duplicate component references', (string) $result['errors'][0]); + } + + public function testValidateBOMEntryWithInvalidDesignatorFormat(): void + { + $entry = [ + 'Designator' => 'R1,invalid,C2', + 'Quantity' => '3', + 'MPN' => 'RES-10K', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertTrue($result['is_valid']); // Warnings don't make it invalid + $this->assertCount(1, $result['warnings']); + $this->assertStringContainsString('unusual format', (string) $result['warnings'][0]); + } + + public function testValidateBOMEntryWithEmptyDesignator(): void + { + $entry = [ + 'Designator' => '', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertGreaterThanOrEqual(1, count($result['errors'])); + $this->assertStringContainsString('Required field "Designator" is missing or empty', implode(' ', array_map('strval', $result['errors']))); + } + + public function testValidateBOMEntryWithInvalidPartDBID(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + 'Part-DB ID' => 'abc', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertFalse($result['is_valid']); + $this->assertGreaterThanOrEqual(1, count($result['errors'])); + $this->assertStringContainsString('not a valid number', implode(' ', array_map('strval', $result['errors']))); + } + + public function testValidateBOMEntryWithNonExistentPartDBID(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + 'Part-DB ID' => '999999', // Use very high ID that doesn't exist + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertTrue($result['is_valid']); // Warnings don't make it invalid + $this->assertCount(1, $result['warnings']); + $this->assertStringContainsString('not found in database', (string) $result['warnings'][0]); + } + + public function testValidateBOMEntryWithNoComponentName(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'Package' => '0603', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertTrue($result['is_valid']); // Warnings don't make it invalid + $this->assertCount(1, $result['warnings']); + $this->assertStringContainsString('No component name/designation', (string) $result['warnings'][0]); + } + + public function testValidateBOMEntryWithLongPackageName(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + 'Package' => str_repeat('A', 150), // Very long package name + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertTrue($result['is_valid']); // Warnings don't make it invalid + $this->assertCount(1, $result['warnings']); + $this->assertStringContainsString('unusually long', (string) $result['warnings'][0]); + } + + public function testValidateBOMEntryWithLibraryPrefix(): void + { + $entry = [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + 'Package' => 'Resistor_SMD:R_0603_1608Metric', + ]; + + $result = $this->validationService->validateBOMEntry($entry, 1); + + $this->assertTrue($result['is_valid']); + $this->assertCount(1, $result['info']); + $this->assertStringContainsString('library prefix', $result['info'][0]); + } + + public function testValidateBOMEntriesWithMultipleEntries(): void + { + $entries = [ + [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + ], + [ + 'Designator' => 'C1,C2', + 'Quantity' => '2', + 'MPN' => 'CAP-100nF', + ], + ]; + + $result = $this->validationService->validateBOMEntries($entries); + + $this->assertTrue($result['is_valid']); + $this->assertEquals(2, $result['total_entries']); + $this->assertEquals(2, $result['valid_entries']); + $this->assertEquals(0, $result['invalid_entries']); + $this->assertCount(2, $result['line_results']); + } + + public function testValidateBOMEntriesWithMixedResults(): void + { + $entries = [ + [ + 'Designator' => 'R1', + 'Quantity' => '1', + 'MPN' => 'RES-10K', + ], + [ + 'Designator' => 'C1,C2', + 'Quantity' => '1', // Mismatch + 'MPN' => 'CAP-100nF', + ], + ]; + + $result = $this->validationService->validateBOMEntries($entries); + + $this->assertFalse($result['is_valid']); + $this->assertEquals(2, $result['total_entries']); + $this->assertEquals(1, $result['valid_entries']); + $this->assertEquals(1, $result['invalid_entries']); + $this->assertCount(1, $result['errors']); + } + + public function testGetValidationStats(): void + { + $validation_result = [ + 'total_entries' => 10, + 'valid_entries' => 8, + 'invalid_entries' => 2, + 'errors' => ['Error 1', 'Error 2'], + 'warnings' => ['Warning 1'], + 'info' => ['Info 1', 'Info 2'], + ]; + + $stats = $this->validationService->getValidationStats($validation_result); + + $this->assertEquals(10, $stats['total_entries']); + $this->assertEquals(8, $stats['valid_entries']); + $this->assertEquals(2, $stats['invalid_entries']); + $this->assertEquals(2, $stats['error_count']); + $this->assertEquals(1, $stats['warning_count']); + $this->assertEquals(2, $stats['info_count']); + $this->assertEquals(80.0, $stats['success_rate']); + } + + public function testGetErrorMessage(): void + { + $validation_result = [ + 'is_valid' => false, + 'errors' => ['Error 1', 'Error 2'], + 'warnings' => ['Warning 1'], + ]; + + $message = $this->validationService->getErrorMessage($validation_result); + + $this->assertStringContainsString('Errors:', $message); + $this->assertStringContainsString('• Error 1', $message); + $this->assertStringContainsString('• Error 2', $message); + $this->assertStringContainsString('Warnings:', $message); + $this->assertStringContainsString('• Warning 1', $message); + } + + public function testGetErrorMessageWithValidResult(): void + { + $validation_result = [ + 'is_valid' => true, + 'errors' => [], + 'warnings' => [], + ]; + + $message = $this->validationService->getErrorMessage($validation_result); + + $this->assertEquals('', $message); + } +} \ No newline at end of file diff --git a/tests/Services/LabelSystem/PlaceholderProviders/TimestampableElementProviderTest.php b/tests/Services/LabelSystem/PlaceholderProviders/TimestampableElementProviderTest.php index a72f06df..6aa152b9 100644 --- a/tests/Services/LabelSystem/PlaceholderProviders/TimestampableElementProviderTest.php +++ b/tests/Services/LabelSystem/PlaceholderProviders/TimestampableElementProviderTest.php @@ -60,26 +60,29 @@ class TimestampableElementProviderTest extends WebTestCase protected function setUp(): void { self::bootKernel(); - \Locale::setDefault('en'); + \Locale::setDefault('en_US'); $this->service = self::getContainer()->get(TimestampableElementProvider::class); - $this->target = new class() implements TimeStampableInterface { + $this->target = new class () implements TimeStampableInterface { public function getLastModified(): ?DateTime { - return new \DateTime('2000-01-01'); + return new DateTime('2000-01-01'); } public function getAddedDate(): ?DateTime { - return new \DateTime('2000-01-01'); + return new DateTime('2000-01-01'); } }; } public static function dataProvider(): \Iterator { - \Locale::setDefault('en'); - yield ['1/1/00, 12:00 AM', '[[LAST_MODIFIED]]']; - yield ['1/1/00, 12:00 AM', '[[CREATION_DATE]]']; + \Locale::setDefault('en_US'); + // Use IntlDateFormatter like the actual service does + $formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT); + $expectedFormat = $formatter->format(new DateTime('2000-01-01')); + yield [$expectedFormat, '[[LAST_MODIFIED]]']; + yield [$expectedFormat, '[[CREATION_DATE]]']; } #[DataProvider('dataProvider')] @@ -87,4 +90,4 @@ class TimestampableElementProviderTest extends WebTestCase { $this->assertSame($expected, $this->service->replace($placeholder, $this->target)); } -} +} \ No newline at end of file diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index c70ad2af..7e896170 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -580,7 +580,7 @@ storelocation.new - Nové místo skladování + Nové umístění @@ -913,7 +913,7 @@ Související prvky budou přesunuty nahoru. edit.log_comment - Změnit komentář + Komentář ke změně @@ -2502,7 +2502,7 @@ Související prvky budou přesunuty nahoru. part.needs_review.badge - Potřeba revize + Vyžaduje kontrolu @@ -4019,7 +4019,7 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn search.regexmatching - RegEx. shoda + Reg.Ex. shoda @@ -4858,7 +4858,7 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn part.table.needsReview - Potřeba revize + Vyžaduje kontrolu @@ -5662,7 +5662,7 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn part.edit.needs_review - Potřeba revize + Vyžaduje kontrolu @@ -6357,7 +6357,7 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn user.theme.label - Téma + Vzhled @@ -6368,7 +6368,7 @@ Pokud jste to provedli nesprávně nebo pokud počítač již není důvěryhodn user_settings.theme.placeholder - Serverové téma + Vzhled pro celý server @@ -9718,7 +9718,7 @@ Element 3 part_list.action.action.group.needs_review - Potřeba revize + Vyžaduje kontrolu @@ -10678,7 +10678,7 @@ Element 3 log.element_edited.changed_fields.theme - Téma + Vzhled @@ -10774,7 +10774,7 @@ Element 3 log.element_edited.changed_fields.needs_review - Potřeba revize + Vyžaduje kontrolu @@ -10984,7 +10984,7 @@ Element 3 parts.import.help - Pomocí tohoto nástroje můžete importovat díly z existujících souborů. Díly budou zapsány přímo do databáze, proto před nahráním souboru sem zkontrolujte, zda je jeho obsah správný. + Pomocí tohoto nástroje můžete importovat součásti z existujících souborů. Součásti budou přímo zapsány do databáze, proto před nahráním souboru zkontrolujte jeho správný obsah. @@ -11014,7 +11014,7 @@ Element 3 parts.import.part_needs_review.help - Pokud je tato možnost vybrána, budou všechny díly označeny jako "Potřeba revize" bez ohledu na to, co bylo nastaveno v údajích. + Pokud je tato možnost vybrána, budou všechny díly označeny jako "Vyžaduje kontrolu" bez ohledu na to, co bylo nastaveno v údajích. @@ -12060,7 +12060,7 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz part.info.withdraw_modal.delete_lot_if_empty - Vymazat tento inventář, až se vyprázdní + Smazat tuto položku, pokud se vyprázdní @@ -12528,7 +12528,7 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz settings.system.customization.instanceName - Instance name + Název instance @@ -12576,7 +12576,7 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz settings.system.customization.theme - Globální téma + Globální vzhed @@ -12642,7 +12642,7 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz settings.system.privacy.useGravatar.description - Pokud uživatel nemá nastavený avatar, použijte avatar z Gravataru na základě e-mailové adresy uživatele. To způsobí, že prohlížeč načte obrázky od třetí strany! + Pokud uživatel nemá zadaný obrázek avatara, použije se avatar z Gravataru na základě e-mailu uživatele. To způsobí, že prohlížeč načte obrázky ze třetí strany! @@ -12691,7 +12691,7 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz settings.system.privacy - Ochrana osobních údajů + Soukromí diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index a5c18cdd..8515abb8 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -6504,7 +6504,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr flash.password_change_needed - Ihr Password muss geändert werden! + Ihr Passwort muss geändert werden! @@ -7157,8 +7157,14 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr mass_creation.lines.placeholder Element 1 + Element 1.1 + Element 1.1.1 + Element 1.2 Element 2 -Element 3 +Element 3 + +Element 1 -> Element 1.1 +Element 1 -> Element 1.2 @@ -9006,7 +9012,7 @@ Element 3 part_list.action.part_count - %count% Bauteile ausgewählt! + %count% Bauteile ausgewählt @@ -12921,7 +12927,7 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön settings.behavior.sidebar.rootNodeRedirectsToNewEntity - Wurzelknoten leitet zur Erstellung eines neuen Elements weiter + Stammknoten leitet zur Erstellung eines neuen Elements weiter @@ -13050,5 +13056,389 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Aus Sicherheitsgründen ausgeblendet + + + project.bom_import.map_fields + Spalten zuordnen + + + + + project.bom_import.map_fields.help + Wählen Sie aus, wie CSV Spalten auf BOM Felder gemappt werden + + + + + project.bom_import.delimiter + Trennzeichen + + + + + project.bom_import.delimiter.comma + Komma (,) + + + + + project.bom_import.delimiter.semicolon + Semikolon (;) + + + + + project.bom_import.delimiter.tab + Tab + + + + + project.bom_import.field_mapping.title + Spaltenzuordnung + + + + + project.bom_import.field_mapping.csv_field + CSV Spalte + + + + + project.bom_import.field_mapping.maps_to + Mappt auf + + + + + project.bom_import.field_mapping.suggestion + Vorschlag + + + + + project.bom_import.field_mapping.priority + Priorität + + + + + project.bom_import.field_mapping.priority_help + Priorität (kleinere Nummer = höhere Priorität) + + + + + project.bom_import.field_mapping.priority_short + P + + + + + project.bom_import.field_mapping.priority_note + Prioritätstipp: Niedrigere Zahlen = höhere Priorität. Die Standardpriorität ist 10. Verwenden Sie die Prioritäten 1–9 für die wichtigsten Felder und 10+ für normale Priorität. + + + + + project.bom_import.field_mapping.summary + Zusammenfassung der Zuordnung + + + + + project.bom_import.field_mapping.select_to_see_summary + Wählen Sie Zuordnungen aus, um eine Zusammenfassung anzuzeigen. + + + + + project.bom_import.field_mapping.no_suggestion + Kein Vorschlag + + + + + project.bom_import.preview + Vorschau + + + + + project.bom_import.flash.session_expired + Die Import-Sitzung ist abgelaufen. Bitte laden Sie Ihre Datei erneut hoch. + + + + + project.bom_import.field_mapping.ignore + Ignorieren + + + + + project.bom_import.type.kicad_schematic + KiCAD Schaltplaneditor BOM (CSV Datei) + + + + + common.back + Zurück + + + + + project.bom_import.validation.errors.required_field_missing + Zeile %line%: Das Pflichtfeld „%field%“ fehlt oder ist leer. Bitte stellen Sie sicher, dass dieses Feld zugeordnet ist und Daten enthält. + + + + + project.bom_import.validation.errors.no_valid_designators + Zeile %line%: Das Bezeichnungsfeld enthält keine gültigen Komponentenreferenzen. Erwartetes Format: „R1,C2,U3“ oder „R1, C2, U3“. + + + + + project.bom_import.validation.warnings.unusual_designator_format + Zeile %line%: Einige Komponentenreferenzen haben möglicherweise ein ungewöhnliches Format: %designators%. Erwartetes Format: „R1“, „C2“, „U3“ usw. + + + + + project.bom_import.validation.errors.duplicate_designators + Zeile %line%: Doppelte Komponentenreferenzen gefunden: %designators%. Jede Komponente sollte nur einmal pro Zeile referenziert werden. + + + + + project.bom_import.validation.errors.invalid_quantity + Zeile %line%: Die Menge „%quantity%“ ist keine gültige Zahl. Bitte geben Sie einen numerischen Wert ein (z. B. 1, 2,5, 10). + + + + + project.bom_import.validation.errors.quantity_zero_or_negative + Zeile %line%: Die Menge muss größer als 0 sein, erhaltene Menge %quantity%. + + + + + project.bom_import.validation.warnings.quantity_unusually_high + Zeile %line%: Die Menge %quantity% erscheint ungewöhnlich hoch. Bitte überprüfen Sie, ob dies korrekt ist. + + + + + project.bom_import.validation.warnings.quantity_not_whole_number + Zeile %line%: Die Menge %quantity% ist keine ganze Zahl, aber Sie haben %count% Komponentenreferenzen. Dies kann auf eine Nichtübereinstimmung hindeuten. + + + + + project.bom_import.validation.errors.quantity_designator_mismatch + Zeile %line%: Diskrepanz zwischen Menge und Komponentenreferenzen. Menge: %quantity%, Referenzen: %count% (%designators%). Diese sollten übereinstimmen. Passen Sie entweder die Menge an oder überprüfen Sie Ihre Komponentenreferenzen. + + + + + project.bom_import.validation.errors.invalid_partdb_id + Zeile %line%: Part-DB ID „%id%“ ist keine gültige Zahl. Bitte geben Sie eine numerische ID ein. + + + + + project.bom_import.validation.errors.partdb_id_zero_or_negative + Zeile %line%: Die Part-DB ID muss größer als 0 sein, erhaltene ID lautet %id%. + + + + + project.bom_import.validation.warnings.partdb_id_not_found + Zeile %line%: Teil-DB-ID %id% nicht in der Datenbank gefunden. Die Komponente wird ohne Verknüpfung mit einem vorhandenen Teil importiert. + + + + + project.bom_import.validation.info.partdb_link_success + Zeile %line%: Erfolgreich mit dem Bauteil „%name%“ (ID: %id%) verknüpft. + + + + + project.bom_import.validation.warnings.no_component_name + Zeile %line%: Kein Komponentenname/keine Komponentenbezeichnung angegeben (MPN, Bezeichnung oder Wert). Die Komponente wird als „Unbekanntes Bauteil” bezeichnet. + + + + + project.bom_import.validation.warnings.package_name_too_long + Zeile %line%: Der Footprintname „%package%“ ist ungewöhnlich lang. Bitte überprüfen Sie, ob er korrekt ist. + + + + + project.bom_import.validation.info.library_prefix_detected + Zeile %line%: Das Footprint „%package%“ enthält ein Bibliothekspräfix. Dieses wird beim Import automatisch entfernt. + + + + + project.bom_import.validation.errors.non_numeric_field + Zeile %line%: Das Feld „%field%“ enthält den nicht numerischen Wert „%value%“. Bitte geben Sie eine gültige Zahl ein. + + + + + project.bom_import.validation.info.import_summary + Importübersicht: %total% Einträge insgesamt, %valid% gültig, %invalid% mit Problemen. + + + + + project.bom_import.validation.errors.summary + Es wurden %count% Validierungsfehler gefunden, die behoben werden müssen, bevor der Import fortgesetzt werden kann. + + + + + project.bom_import.validation.warnings.summary + Es wurden %count% Warnungen gefunden. Bitte überprüfen Sie diese Probleme, bevor Sie fortfahren. + + + + + project.bom_import.validation.info.all_valid + Alle Einträge haben die Validierung erfolgreich bestanden! + + + + + project.bom_import.validation.summary + Validierungsübersicht + + + + + project.bom_import.validation.total_entries + Gesamtzahl der Einträge + + + + + project.bom_import.validation.valid_entries + Gültige Einträge + + + + + project.bom_import.validation.invalid_entries + Ungültige Einträge + + + + + project.bom_import.validation.success_rate + Erfolgsquote + + + + + project.bom_import.validation.errors.title + Validierungsfehler + + + + + project.bom_import.validation.errors.description + Die folgenden Fehler müssen behoben werden, bevor der Import fortgesetzt werden kann: + + + + + project.bom_import.validation.warnings.title + Validierungswarnungen + + + + + project.bom_import.validation.warnings.description + Die folgenden Warnhinweise sollten vor dem Fortfahren gelesen werden: + + + + + project.bom_import.validation.info.title + Informationen + + + + + project.bom_import.validation.details.title + Detaillierte Validierungsergebnisse + + + + + project.bom_import.validation.details.line + Zeile + + + + + project.bom_import.validation.details.status + Status + + + + + project.bom_import.validation.details.messages + Meldungen + + + + + project.bom_import.validation.details.valid + Gültig + + + + + project.bom_import.validation.details.invalid + Ungültig + + + + + project.bom_import.validation.all_valid + Alle Einträge sind gültig und bereit zum Import! + + + + + project.bom_import.validation.fix_errors + Bitte beheben Sie die Validierungsfehler, bevor Sie mit dem Import fortfahren. + + + + + project.bom_import.type.generic_csv + Generische CSV-Datei + + + + + label_generator.update_profile + Profil mit aktuellen Einstellungen aktualisieren + + + + + label_generator.profile_updated + Labelprofil aktualisiert + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index e65445ce..41ad8358 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -7164,8 +7164,8 @@ Exampletown Element 2 Element 3 -Element 1 -> Element 1.1 -Element 1 -> Element 1.2]]> +Element 1 -> Element 1.1 +Element 1 -> Element 1.2]]> @@ -12333,7 +12333,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.ips.element14.apiKey.help - You can register for an API key on <a href="https://partner.element14.com/">https://partner.element14.com/</a>. + https://partner.element14.com/.]]> @@ -12345,7 +12345,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.ips.element14.storeId.help - The store domain to retrieve the data from. This decides the language and currency of results. See <a href="https://partner.element14.com/docs/Product_Search_API_REST__Description">here</a> for a list of valid domains. + here for a list of valid domains.]]> @@ -12363,7 +12363,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.ips.tme.token.help - You can get an API token and secret on <a href="https://developers.tme.eu/en/">https://developers.tme.eu/en/</a>. + https://developers.tme.eu/en/.]]> @@ -12411,7 +12411,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.ips.mouser.apiKey.help - You can register for an API key on <a href="https://eu.mouser.com/api-hub/">https://eu.mouser.com/api-hub/</a>. + https://eu.mouser.com/api-hub/.]]> @@ -12489,7 +12489,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.system.attachments - Attachments & Files + @@ -12513,7 +12513,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.system.attachments.allowDownloads.help - With this option users can download external files into Part-DB by providing an URL. <b>Attention: This can be a security issue, as it might allow users to access intranet ressources via Part-DB!</b> + Attention: This can be a security issue, as it might allow users to access intranet ressources via Part-DB!]]> @@ -12687,8 +12687,8 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.system.localization.base_currency_description - The currency that is used to store price information and exchange rates in. This currency is assumed, when no currency is set for a price information. -<b>Please note that the currencies are not converted, when changing this value. So changing the default currency after you already added price information, will result in wrong prices!</b> + Please note that the currencies are not converted, when changing this value. So changing the default currency after you already added price information, will result in wrong prices!]]> @@ -12718,7 +12718,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.misc.kicad_eda.category_depth.help - This value determines the depth of the category tree, that is visible inside KiCad. 0 means that only the top level categories are visible. Set to a value > 0 to show more levels. Set to -1, to show all parts of Part-DB inside a sigle cnategory in KiCad. + 0 to show more levels. Set to -1, to show all parts of Part-DB inside a sigle cnategory in KiCad.]]> @@ -12736,7 +12736,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.behavior.sidebar.items.help - The menus which appear at the sidebar by default. Order of items can be changed via drag & drop. + @@ -12784,7 +12784,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.behavior.table.parts_default_columns.help - The columns to show by default in part tables. Order of items can be changed via drag & drop. + @@ -12838,7 +12838,7 @@ Please note, that you can not impersonate a disabled user. If you try you will g settings.ips.oemsecrets.sortMode.M - Completeness & Manufacturer name + @@ -13057,5 +13057,389 @@ Please note, that you can not impersonate a disabled user. If you try you will g Redacted for security reasons + + + project.bom_import.map_fields + Map Fields + + + + + project.bom_import.map_fields.help + Configure how CSV columns map to BOM fields + + + + + project.bom_import.delimiter + Delimiter + + + + + project.bom_import.delimiter.comma + Comma (,) + + + + + project.bom_import.delimiter.semicolon + Semicolon (;) + + + + + project.bom_import.delimiter.tab + Tab + + + + + project.bom_import.field_mapping.title + Field Mapping + + + + + project.bom_import.field_mapping.csv_field + CSV Field + + + + + project.bom_import.field_mapping.maps_to + Maps To + + + + + project.bom_import.field_mapping.suggestion + Suggestion + + + + + project.bom_import.field_mapping.priority + Priority + + + + + project.bom_import.field_mapping.priority_help + Priority (lower number = higher priority) + + + + + project.bom_import.field_mapping.priority_short + P + + + + + project.bom_import.field_mapping.priority_note + Priority Tip: Lower numbers = higher priority. Default priority is 10. Use priorities 1-9 for most important fields, 10+ for normal priority. + + + + + project.bom_import.field_mapping.summary + Field Mapping Summary + + + + + project.bom_import.field_mapping.select_to_see_summary + Select field mappings to see summary + + + + + project.bom_import.field_mapping.no_suggestion + No suggestion + + + + + project.bom_import.preview + Preview + + + + + project.bom_import.flash.session_expired + Import session has expired. Please upload your file again. + + + + + project.bom_import.field_mapping.ignore + Ignore + + + + + project.bom_import.type.kicad_schematic + KiCAD Schematic BOM (CSV file) + + + + + common.back + Back + + + + + project.bom_import.validation.errors.required_field_missing + Line %line%: Required field "%field%" is missing or empty. Please ensure this field is mapped and contains data. + + + + + project.bom_import.validation.errors.no_valid_designators + Line %line%: Designator field contains no valid component references. Expected format: "R1,C2,U3" or "R1, C2, U3". + + + + + project.bom_import.validation.warnings.unusual_designator_format + Line %line%: Some component references may have unusual format: %designators%. Expected format: "R1", "C2", "U3", etc. + + + + + project.bom_import.validation.errors.duplicate_designators + Line %line%: Duplicate component references found: %designators%. Each component should be referenced only once per line. + + + + + project.bom_import.validation.errors.invalid_quantity + Line %line%: Quantity "%quantity%" is not a valid number. Please enter a numeric value (e.g., 1, 2.5, 10). + + + + + project.bom_import.validation.errors.quantity_zero_or_negative + Line %line%: Quantity must be greater than 0, got %quantity%. + + + + + project.bom_import.validation.warnings.quantity_unusually_high + Line %line%: Quantity %quantity% seems unusually high. Please verify this is correct. + + + + + project.bom_import.validation.warnings.quantity_not_whole_number + Line %line%: Quantity %quantity% is not a whole number, but you have %count% component references. This may indicate a mismatch. + + + + + project.bom_import.validation.errors.quantity_designator_mismatch + Line %line%: Mismatch between quantity and component references. Quantity: %quantity%, References: %count% (%designators%). These should match. Either adjust the quantity or check your component references. + + + + + project.bom_import.validation.errors.invalid_partdb_id + Line %line%: Part-DB ID "%id%" is not a valid number. Please enter a numeric ID. + + + + + project.bom_import.validation.errors.partdb_id_zero_or_negative + Line %line%: Part-DB ID must be greater than 0, got %id%. + + + + + project.bom_import.validation.warnings.partdb_id_not_found + Line %line%: Part-DB ID %id% not found in database. The component will be imported without linking to an existing part. + + + + + project.bom_import.validation.info.partdb_link_success + Line %line%: Successfully linked to Part-DB part "%name%" (ID: %id%). + + + + + project.bom_import.validation.warnings.no_component_name + Line %line%: No component name/designation provided (MPN, Designation, or Value). Component will be named "Unknown Component". + + + + + project.bom_import.validation.warnings.package_name_too_long + Line %line%: Package name "%package%" is unusually long. Please verify this is correct. + + + + + project.bom_import.validation.info.library_prefix_detected + Line %line%: Package "%package%" contains library prefix. This will be automatically removed during import. + + + + + project.bom_import.validation.errors.non_numeric_field + Line %line%: Field "%field%" contains non-numeric value "%value%". Please enter a valid number. + + + + + project.bom_import.validation.info.import_summary + Import summary: %total% total entries, %valid% valid, %invalid% with issues. + + + + + project.bom_import.validation.errors.summary + Found %count% validation error(s) that must be fixed before import can proceed. + + + + + project.bom_import.validation.warnings.summary + Found %count% warning(s). Please review these issues before proceeding. + + + + + project.bom_import.validation.info.all_valid + All entries passed validation successfully! + + + + + project.bom_import.validation.summary + Validation Summary + + + + + project.bom_import.validation.total_entries + Total Entries + + + + + project.bom_import.validation.valid_entries + Valid Entries + + + + + project.bom_import.validation.invalid_entries + Invalid Entries + + + + + project.bom_import.validation.success_rate + Success Rate + + + + + project.bom_import.validation.errors.title + Validation Errors + + + + + project.bom_import.validation.errors.description + The following errors must be fixed before the import can proceed: + + + + + project.bom_import.validation.warnings.title + Validation Warnings + + + + + project.bom_import.validation.warnings.description + The following warnings should be reviewed before proceeding: + + + + + project.bom_import.validation.info.title + Information + + + + + project.bom_import.validation.details.title + Detailed Validation Results + + + + + project.bom_import.validation.details.line + Line + + + + + project.bom_import.validation.details.status + Status + + + + + project.bom_import.validation.details.messages + Messages + + + + + project.bom_import.validation.details.valid + Valid + + + + + project.bom_import.validation.details.invalid + Invalid + + + + + project.bom_import.validation.all_valid + All entries are valid and ready for import! + + + + + project.bom_import.validation.fix_errors + Please fix the validation errors before proceeding with the import. + + + + + project.bom_import.type.generic_csv + Generic CSV + + + + + label_generator.update_profile + Update profile with current settings + + + + + label_generator.profile_updated + Label profile updated successfully. + + diff --git a/yarn.lock b/yarn.lock index 307692f2..3289c949 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,66 +2,58 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.19.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz#702df67a08cb3cfe8c33ee1111ef136ec1a9e232" - integrity sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw== +"@algolia/autocomplete-core@1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.19.3.tgz#f480d638d2b4218f8161f313186db7a5aac99c90" + integrity sha512-45CVTxtd3PwVux5G3WLUA3So5tRKRXu+amupW0dg3KTaTeydt+KzvH1mrZhs3hUne7VQ+g8+ZRGWHbuL/Rb5mw== dependencies: - "@algolia/autocomplete-plugin-algolia-insights" "1.19.2" - "@algolia/autocomplete-shared" "1.19.2" + "@algolia/autocomplete-plugin-algolia-insights" "1.19.3" + "@algolia/autocomplete-shared" "1.19.3" -"@algolia/autocomplete-js@1.19.2", "@algolia/autocomplete-js@^1.17.0": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.19.2.tgz#3768a501671b43923aee8c111680d3738c432215" - integrity sha512-pUElPLQypSGwewihADgV/g57EWepn/jHoArnbtyJNvn4onJCDwmJGelCm5+dN/3dAYZq7QO2ExFEjGsoiG/nUg== +"@algolia/autocomplete-js@1.19.3", "@algolia/autocomplete-js@^1.17.0": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.19.3.tgz#a3f733ac654201beb18c29e83b61653e5037c04c" + integrity sha512-uJPElcGy1jqi8WAzTBgX4xufu+cRYSaDfAZW3ed4AVTOu8oDwUkMgrKgpKxp5u8d6BhugSm47vGkYoj87jZQ/Q== dependencies: - "@algolia/autocomplete-core" "1.19.2" - "@algolia/autocomplete-preset-algolia" "1.19.2" - "@algolia/autocomplete-shared" "1.19.2" + "@algolia/autocomplete-core" "1.19.3" + "@algolia/autocomplete-preset-algolia" "1.19.3" + "@algolia/autocomplete-shared" "1.19.3" htm "^3.1.1" preact "^10.13.2" -"@algolia/autocomplete-plugin-algolia-insights@1.19.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz#3584b625b9317e333d1ae43664d02358e175c52d" - integrity sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg== +"@algolia/autocomplete-plugin-algolia-insights@1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.3.tgz#04e6e8150cd0964f7521acbb1eb1a3d650e9f60d" + integrity sha512-Oy6t0Ws99xWKCzrp7pFWncLqFA3MoBAv1DDbDrn2XN9NBE9GviXw2hZsBi6CFReR/9wK72xq4vT96LBshOxhaQ== dependencies: - "@algolia/autocomplete-shared" "1.19.2" + "@algolia/autocomplete-shared" "1.19.3" "@algolia/autocomplete-plugin-recent-searches@^1.17.0": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-recent-searches/-/autocomplete-plugin-recent-searches-1.19.2.tgz#59341b2b6e121fedd1ab3e1652d86630f4c37fc4" - integrity sha512-V4VYzv0wvsBYsGxDcicpY17YRvayiFnMl24/kNAEBdIsxtF555Yfg0CHAmR55JdZRs9er/op1SOBpcc5+3V76g== + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-recent-searches/-/autocomplete-plugin-recent-searches-1.19.3.tgz#f6a98362dc7d7fcf080b17202dd8b6207fe447d2" + integrity sha512-RfY6TyolCa2gV655EKsz5sMp7E19C59ENJ3LBe5lRyq3o6sO5jNAMMyEBAp7y8M7uGRdepa6Y7Tch1zSLlCEEw== dependencies: - "@algolia/autocomplete-core" "1.19.2" - "@algolia/autocomplete-js" "1.19.2" - "@algolia/autocomplete-preset-algolia" "1.19.2" - "@algolia/autocomplete-shared" "1.19.2" + "@algolia/autocomplete-core" "1.19.3" + "@algolia/autocomplete-js" "1.19.3" + "@algolia/autocomplete-preset-algolia" "1.19.3" + "@algolia/autocomplete-shared" "1.19.3" -"@algolia/autocomplete-preset-algolia@1.19.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.19.2.tgz#c6c1e1ff7b011090a70e66b02e6db4ebade4535e" - integrity sha512-/Z9tDn84fnyUyjajvWRskOX7p/BDKK5PidEA4Y/aAl0c6VfHu5dMkTDG090CIiskLUgpkHacLbz+A10gMBP++Q== +"@algolia/autocomplete-preset-algolia@1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.19.3.tgz#027fd0937bc22b72c3eecf56053ab55f79e4b423" + integrity sha512-NIvRLWFnX5MclQVyRKPwNDxjNg214qXCTZ/jLLVXw17VmPsEYfgeSYEMWEGFapA8KKKMz+Kwb+nBOc4je6DXfg== dependencies: - "@algolia/autocomplete-shared" "1.19.2" + "@algolia/autocomplete-shared" "1.19.3" -"@algolia/autocomplete-shared@1.19.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz#c0b7b8dc30a5c65b70501640e62b009535e4578f" - integrity sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w== +"@algolia/autocomplete-shared@1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.3.tgz#9bd9dfd80fa3e320461682e917f0f94404f60eba" + integrity sha512-zzpqoVm/I4eRFT5Mcempwa5SVKox83eVIsZyLAYQdV+7tmtEYayx225Kl7nwhGrJ7NCozE9YWMwuFFN2g5dSBg== "@algolia/autocomplete-theme-classic@^1.17.0": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.19.2.tgz#7c2a7d8f74988536f0a2f6ae806b18f915523266" - integrity sha512-UapO6bGuT5NkRK8VWxSg8AOLRhIcxBZ/OYg7ao//WHBo/yyiDybxy+K/xeY1RcHQVgimqlWfXj8IWAyQxxZP6A== - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" + version "1.19.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.19.3.tgz#b4442911e3dc38bfb40c25f56b71f099a321f9c7" + integrity sha512-f0s9AxiqWTrv+etLcVXqzBTX5QbnR6JXJPmWu5mgkch7VY4AIqIuNB8ToDkSl1Hp9prkKir7/J9xEf7BDePHww== "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": version "7.27.1" @@ -73,25 +65,25 @@ picocolors "^1.1.1" "@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" - integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== "@babel/core@^7.19.6": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" - integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== dependencies: - "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.27.1" "@babel/generator" "^7.28.3" "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.3" - "@babel/parser" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -252,20 +244,20 @@ "@babel/traverse" "^7.28.3" "@babel/types" "^7.28.2" -"@babel/helpers@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.3.tgz#b83156c0a2232c133d1b535dd5d3452119c7e441" - integrity sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw== +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== dependencies: "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" + "@babel/types" "^7.28.4" -"@babel/parser@^7.18.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" - integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== +"@babel/parser@^7.18.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== dependencies: - "@babel/types" "^7.28.2" + "@babel/types" "^7.28.4" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": version "7.27.1" @@ -366,9 +358,9 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-block-scoping@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz#e7c50cbacc18034f210b93defa89638666099451" - integrity sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz#e19ac4ddb8b7858bac1fd5c1be98a994d9726410" + integrity sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A== dependencies: "@babel/helper-plugin-utils" "^7.27.1" @@ -389,16 +381,16 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-classes@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz#598297260343d0edbd51cb5f5075e07dee91963a" - integrity sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" + integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== dependencies: "@babel/helper-annotate-as-pure" "^7.27.3" "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-globals" "^7.28.0" "@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-replace-supers" "^7.27.1" - "@babel/traverse" "^7.28.3" + "@babel/traverse" "^7.28.4" "@babel/plugin-transform-computed-properties@^7.27.1": version "7.27.1" @@ -577,15 +569,15 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-object-rest-spread@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz#d23021857ffd7cd809f54d624299b8086402ed8d" - integrity sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" + integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== dependencies: "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-destructuring" "^7.28.0" "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.0" + "@babel/traverse" "^7.28.4" "@babel/plugin-transform-object-super@^7.27.1": version "7.27.1" @@ -642,9 +634,9 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-regenerator@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz#b8eee0f8aed37704bbcc932fd0b1a0a34d0b7344" - integrity sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A== + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" + integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== dependencies: "@babel/helper-plugin-utils" "^7.27.1" @@ -824,180 +816,180 @@ "@babel/parser" "^7.27.2" "@babel/types" "^7.27.1" -"@babel/traverse@^7.18.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" - integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== +"@babel/traverse@^7.18.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== dependencies: "@babel/code-frame" "^7.27.1" "@babel/generator" "^7.28.3" "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.3" + "@babel/parser" "^7.28.4" "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" + "@babel/types" "^7.28.4" debug "^4.3.1" -"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.4.4": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" - integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.4.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" -"@ckeditor/ckeditor5-adapter-ckfinder@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-46.0.2.tgz#a165fc259e91189d4f13cc83fc11f7f7e0c6a1b7" - integrity sha512-S4VO8l+WS8yVGpu9vB00rWNdFIR4NTAkuCP7iLlodB45KFgMobP1GTqF8EqNFIJEU2PHJz24R0kcsOyvfU6V/A== +"@ckeditor/ckeditor5-adapter-ckfinder@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-46.0.3.tgz#f19f9fa1a0a33aa2fa502f0f7c779c027f4f78bd" + integrity sha512-xebONgXYuF8Fuhr6C+lpwRSfpChSrJKTy5S0i7vuBY+EeuXLRED7AuCOvPwV9oed1/CqbzDWWH1IefgkLwZwvQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-upload" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-upload" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-alignment@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-46.0.2.tgz#68ce011f01e4bed205e8ee3cd2599a54b89af19a" - integrity sha512-iCVJIkmJ+DT2Podmc0gH8Ntj7rYr9kziYLup1VHo/k8mKPfqC3a6o6ngT8ZtPdr1nZ4h4kozVjF+ge2BqnxzmQ== +"@ckeditor/ckeditor5-alignment@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-46.0.3.tgz#34cb75002fefc79dbffc94b08c0a0a34e722adb5" + integrity sha512-P0qegTFO9u5gbR7Ig/JI0vGdWFtxzM08KPCbeYTpQtdI9+DrKdvWFo0LVB7LJjR6OKuUPCtnulGgCyhuzNT7lw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-autoformat@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-46.0.2.tgz#9153d5186a4e5ddcb27b04a181fd21c18625e830" - integrity sha512-IMEWvgRCYw4PkUsshIb7V54fqJvLLohFLH+CQ0RtjzGE8ZYDkuusu7cHDz8hgQwlDWH5X7VOvTdEdPzb0uRhjA== +"@ckeditor/ckeditor5-autoformat@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-46.0.3.tgz#ac2390550211aa71b7065559d4d9c135e3296ad0" + integrity sha512-E3bjlf8HbTD9FiGHPQyrbRXniA7W06CecmlKXwHDisGC8lLLF8ZpuRX4oGAH5QLpSVFyGuj0C1GJtVY0+PEjOw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-heading" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-heading" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-autosave@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-46.0.2.tgz#bcf3f2c44a5341c196343ace454992a3f36468d8" - integrity sha512-DKUCaGzbpwJC4FdWLVQivjJAkOkNqAaCv4+xNESPQvq8pGzBqHPFTZl0ZBvGUxEUj7S1dypIHkVWqRywSNsKJg== +"@ckeditor/ckeditor5-autosave@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-46.0.3.tgz#d26d0157ebf4005fac8f802de16ce65188e64c92" + integrity sha512-SStt6opEniy0i5N5QMsAttpxhPvlmQ5UgmfvVmkyBnvOGwFwSmIFjxAXdTsAhvKdDaKrsjeCpv/j6L6llYk7dw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-basic-styles@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-46.0.2.tgz#cc38af0cfb968911ee3ce302f1ae6a2e3dd5c57e" - integrity sha512-KFMNihlxg7LG7wKhG9OgAOqY621qkdz9clzLPmaoZzFydDfoVlnumFlC3cLnhIK1HOJvDnUec3u9te49pbqllQ== +"@ckeditor/ckeditor5-basic-styles@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-46.0.3.tgz#563cb4ef19ecfd763745cb0bd79940fd03b7a81c" + integrity sha512-THmEPEbYopSfq8NTAugPLk+QW8/vuRkJfg/NpESzeugqCkBG2to3thOHdetbpye4IJBokLFhLsGFfKVYfVF81A== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-block-quote@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-46.0.2.tgz#c7498ac1b588160703a14ccdfc2fe46aba187060" - integrity sha512-QWfqWPFQ4xFSzVgX8L3XqYYnUZE8/p3K23a2S35jwUJRrJl7PzyDNtzqbqohVWn5mGRXlO66qHdbyayrHTx0Lw== +"@ckeditor/ckeditor5-block-quote@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-46.0.3.tgz#79a783d36ad4f9163cc31fb608ac6213c040a145" + integrity sha512-8bI7GoxOPrIExt/32gxLDQJB5VdSp3Oi6fqA+GH0Lqj+ri8HKfl3S147GymTUfBh01IOymQNL7xX04Dq1Nbl6A== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-bookmark@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-bookmark/-/ckeditor5-bookmark-46.0.2.tgz#21fc328e4da97b8a72cd9e9bf13b1cc78e381273" - integrity sha512-qtWBf55fyogvgwR/ftHPT6paMtqWKs1nKMxFkJI2ZAYkd7R1E8YYDmZGNjzbYTCRf8NLxJn6bBc9FCwZUfSxeA== +"@ckeditor/ckeditor5-bookmark@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-bookmark/-/ckeditor5-bookmark-46.0.3.tgz#f597408d87746105ba5d7a80ce8a7f4fa32a7cb6" + integrity sha512-f1usHplw2Ndhm1AiyjWfOWoaSQehMqBaXTa94OXlvO6ci1RIijdFm+DKn4Lgh/vSjv4vo25eQReTmEM0KaysvA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-link" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-link" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-ckbox@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-46.0.2.tgz#9edef4293edc19dc7317ce9d378fa7fab633daf4" - integrity sha512-Q2oqIktjDFi8X2fCE9oELZH02USd4QDcPUShUPRnr/FWcUllx3nXDhz/O+i4bvSh6ckSQKyneRlDtIx11bDbuQ== +"@ckeditor/ckeditor5-ckbox@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-46.0.3.tgz#e0999969662c56bc768ac0ee7a4b09a3f6fefb82" + integrity sha512-UnmCqOU/iyYDef/OVsWbixeXwo+0pb3YGNWgmd2YsCFUUerbpOkDwwGuvCZPE7Hs34lNz8ybbhjR9KmGu8WcAw== dependencies: - "@ckeditor/ckeditor5-cloud-services" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-image" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-upload" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-cloud-services" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-image" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-upload" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" blurhash "2.0.5" - ckeditor5 "46.0.2" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-ckfinder@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-46.0.2.tgz#658d361a64460927681e18360f8b10bd6b3df7ae" - integrity sha512-TC2ZIm1klZ6ZGP1aSbgqiQ6E4fx74pCGqtX5zj+Uk3E3yD48Yr7Wg4dO3eeKcVanIM2MRzg2kr2pGJVlTPcjUw== +"@ckeditor/ckeditor5-ckfinder@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-46.0.3.tgz#f7a0c234be03f71229461668dc8a659f608ecdca" + integrity sha512-VXggqo2w0TgFPyu6z+uH3aTWQMhbq2F2iPUi8SreYCL0JclczbU4HDKqzQU+RKhrzp+yhK1n7ztX5aN1H9EVAw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-image" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-image" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-clipboard@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-46.0.2.tgz#04b87859a599afe2204dd208d4e43d0cc2205e7a" - integrity sha512-FL1Dy3CWRmdMrk31oCpYi9FZew3okXlfgkfLyjbXIgAdUiJ+b/9Tu2ZzR6fNjpAN6BYTiOjx5cDq8h8yMLUgwg== +"@ckeditor/ckeditor5-clipboard@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-46.0.3.tgz#5a42799228875a8112c98fc61ad1ca050f42fca0" + integrity sha512-ECz2goSbYZSlhRT2HszIPCMWFfThA0uIuXpI5PjYj7rDJUoip/Y3/UZjyMo47IUFf66Y4VdvJoq0fv/Z86HYIg== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-cloud-services@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-46.0.2.tgz#edfca0c1c1661d3c0e6421a4aafcbbcb86a6c3f8" - integrity sha512-auY6i4FCrdUiRCOGPUnIEcISKQad7rUm2fkjWHtS89v9sWabDq6BWLyuAFH8HNGjb81csrwb6b2bzMAL7M1rng== +"@ckeditor/ckeditor5-cloud-services@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-46.0.3.tgz#7c02822ed77a1b4d3e80c0f70b4b250c5e946945" + integrity sha512-eKmtcygKoAoba6LGKdsFQyU50yZeeFgD9k05HYnN4BZCqZjrmlTbo3mQrTREgM/w2yxQ4AkDVj162S9NOyibWA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-code-block@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-46.0.2.tgz#c5018d9041228197d3796b558bf3e61827f506fe" - integrity sha512-ADNMDWSmlvrle0j9vNR5WMNyWjVn8t1TVILmLOab2T0/LTZcTzFXdz5i6I/oKhoxKty7soB8lmCUfJqrXNIhTw== +"@ckeditor/ckeditor5-code-block@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-46.0.3.tgz#a8595063ce34da2a2095e89cf79be8b0532de056" + integrity sha512-5Bny1t2jb+Fruy4Tf0Es6YGPe24eWUiCskTv7QZkebEUtectUhZXjrbAPXkn9GQH9E+jU/ywhYkkCKwDgg+Vnw== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-core@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-core/-/ckeditor5-core-46.0.2.tgz#73b20ff36d4900605f4855fcd4cd0a5769027894" - integrity sha512-nXFO2hlmz6gkGzt2/C1yqxwxNqmHxvHy3npIiIuVHWE+e+Zx1BzJjjNEUoZ/K9+6IW0uybhidzGdpdwS6apfpg== +"@ckeditor/ckeditor5-core@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-core/-/ckeditor5-core-46.0.3.tgz#e9d294b517f646d6efdccecc8b3dc030feac7641" + integrity sha512-J03+XnTDL+Ex43ttT4fBxfJGRQxDor0zJc3TxlX44g0q7xD1l7T2CIkorry+817e3By3Qe3DfiMSleHKuDnmvQ== dependencies: - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-watchdog" "46.0.2" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-watchdog" "46.0.3" es-toolkit "1.39.5" "@ckeditor/ckeditor5-dev-translations@^43.0.1", "@ckeditor/ckeditor5-dev-translations@^43.1.0": @@ -1041,316 +1033,316 @@ terser-webpack-plugin "^4.2.3" through2 "^3.0.1" -"@ckeditor/ckeditor5-easy-image@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-46.0.2.tgz#2900e18d8a193fda3a6f7698e3db5d9438c1fc46" - integrity sha512-TjSbCEd8x31k4IlZZmEXA76LW9l1IGzq/bIBX4lLjSF+X30XYVqn9jYzJnPzZ73dNZ1mbzL4gzWO20TaCNyTuA== +"@ckeditor/ckeditor5-easy-image@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-46.0.3.tgz#fbf72ea4524ded6b5aceacc41fa6f5e08672f7f3" + integrity sha512-UZs1G2wZaUr4lJSUsECBpM5ntr0UIXhGYG6lhE4Lf1TBaOypzxusR0H3txNtWIX1rq6hCeFH1P7meijfvJRgbw== dependencies: - "@ckeditor/ckeditor5-cloud-services" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-upload" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-cloud-services" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-upload" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-editor-balloon@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-46.0.2.tgz#dc0b0785aeb6e9266a205d255668aa0269a28207" - integrity sha512-ZZMFkZ1xP+O3JDFP03fsWZXrPbbzzV0ut2cyHvmTbvxsL8nWkByArbAyc4qs7ceF6wQ68PqLk1o+sPkEWHdVnw== +"@ckeditor/ckeditor5-editor-balloon@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-46.0.3.tgz#35382c0393babc1a5f3ec8acd9a0f68ebb56a291" + integrity sha512-NXqmQK45DybJmgWFUln2uTvWqg77BuTp/R/4F33K6fgA4QGmnlWZ+l96Z5Rpmq6Rxc7suBNIKKWRFihquHw1hw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-classic@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-46.0.2.tgz#5bdb980fd5b1cf995c467279e1779307e5c1f52a" - integrity sha512-LTgCEyKapUURBZHZ2y5Z5nmPrl1zl8+kTiTgtpUOgZMQURq/G5BLxx5fdSyF2P0pZAoDYbrDR4uc2ngMH+6lgg== +"@ckeditor/ckeditor5-editor-classic@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-46.0.3.tgz#f872b541014dc24b3a3ff62331a785348ea3ae40" + integrity sha512-fw4pdBqT1UpVYkBBpACQn9w5iR2Y62AvGW7ANt6b1nv55+FIN0uEAHsuChvZdFra8iJQR1qyilT24LVOTtk5mg== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-decoupled@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-46.0.2.tgz#2d3a3a0b0a831ac03a7a1969a9cbdc2a80597439" - integrity sha512-eunAH7bAC7Y0FkxK9ukecG2a7Jxm0NAXlaDIWBRBYmNOycUDnMjeD54Ax4udJ7SxJXiTFYYF6fUIZ/mQy/DHbQ== +"@ckeditor/ckeditor5-editor-decoupled@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-46.0.3.tgz#dae17ccb2d3fc3461fbe174b45590f9cde8748be" + integrity sha512-svrTpgGCi9YLhzit97i+A+lVStnQ4fNbGj6O1HlRG676BA20zqUkUWbNDPlBQT5sbq4N2oLKPwBmAqtUsF9ivQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-inline@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-46.0.2.tgz#7fb1c9b1b5aad15612c56b179b91ad4564600e89" - integrity sha512-XYERPRnt/KNSje/AXpT0aCr6BLpSDAXaGil7edmuPL09oC+gGfjEzvCJDyDHbPCEwOTu684AHVvjiJNKJiJOTQ== +"@ckeditor/ckeditor5-editor-inline@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-46.0.3.tgz#31342902ec3ad3185cfaf8097d55f1086f8f63a6" + integrity sha512-VfsD95gALQrUMHRJ5f2KKIPgtRb5flAqug85GSWy+wJZXOv7dC953tc1v8PYtUOHV6R3k2SWOUAGUClRu2ijOQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-editor-multi-root@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-46.0.2.tgz#03f8d2bf50037c66cddb0ac52b18f4fe3be59c38" - integrity sha512-QUHS10vQ+9XqRfe/djzD6P4Q8rFav3ewXldW2D5trMpQ+d9HzpyyGnYOOHzM5P8VSpgXm1ma8lTuXtqeLnIhnw== +"@ckeditor/ckeditor5-editor-multi-root@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-46.0.3.tgz#b9d9b4f62d5396e3597c24f6183ab92ea0512d52" + integrity sha512-mS9gd8zTCclstU5DROT5L3sVq6HSDk0jw/7d7bgKEvWbGvQ6iPiqcgZ+bzpyrtvXMQKnmgfytZpU9qfODLpwFA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-emoji@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-emoji/-/ckeditor5-emoji-46.0.2.tgz#e71825e85411b1de6d88503e12b6084d41adbdaa" - integrity sha512-ZxjWu2JxnvX8ZyMQpmJ5VpaoXXtWWJxiO6MNeWjL/tcZ2DhD6/lQye7CLuAOvW4P5WBwrGKDdnk+vx7GLO6NIA== +"@ckeditor/ckeditor5-emoji@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-emoji/-/ckeditor5-emoji-46.0.3.tgz#e129445b3a078b19268482b55dd769449922d636" + integrity sha512-XiQsDeIZdSRDuFz/eoH16L21+Ucxykt+qHvqHSXB6bnVE8A3+65fxXYXicXnlb8st6UYhVBGwd53cpRz1ljMww== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-mention" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-mention" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" fuzzysort "3.1.0" -"@ckeditor/ckeditor5-engine@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-46.0.2.tgz#4f215a5f729f6c43b7dca0c8034ae7e9e30036a3" - integrity sha512-KrOmMtfLON/5EFS7x8GgCTRfVE4rFniPCRfBPzNL6rA/eWOclLYvwUGHpI6+JAymZ5XzyPLb8ftn6KjG8vvC+w== +"@ckeditor/ckeditor5-engine@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-46.0.3.tgz#a4d740ad4cd87aa5c2dedbf45bc60f8cad8f4823" + integrity sha512-U5BMV3pZTViU2ArsmmvfzqG1dt03laxgWtX8y2TtoEhaL+cNnT4N2cxj0StioeTbGAP3imkNKvVfRpRBhJIp/Q== dependencies: - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-utils" "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-enter@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-46.0.2.tgz#db3383f5310b8f2a22689abb96e882f2bd8b24a6" - integrity sha512-AZ+WhDEWDH4Ss6i7zd/YcuszlF5QKfkbGPQVsymsUziDvD/IuIQ1WtTDvLfdXbxGKI7amp9e1HCoilOJfv5uDw== +"@ckeditor/ckeditor5-enter@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-46.0.3.tgz#d511f822b98644c8c3d614930184c7df845083c3" + integrity sha512-Z/IVe2Bn/PXamXxTlG9Pf/4K1OoGsNpwBfdywiqSYxdlF5E/4e5xArCKuFVkLGPO2YPSXShPhucBorqHlGQI2Q== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" -"@ckeditor/ckeditor5-essentials@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-46.0.2.tgz#f21b2b2033e71ddd28519c69d89b983bb5c02701" - integrity sha512-ckcjNJiT1KDfllMr6eiBO9t1GlQUELXotjvUW1H93+g87qvl2yFJa/WB7PCpFOc5Derq45/OQWGL5hjySAqGUA== +"@ckeditor/ckeditor5-essentials@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-46.0.3.tgz#56a0b982fe52c8ba605773cfb2c3f0f901849bb3" + integrity sha512-lUk+AkDVXb0YXEbyw+14sA5vFtXoWA4i6026tyN8I9uShMIyyjzkVUtTX9a0AWp5j//sJ5Ke+wMS0QUFRDtj+Q== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-select-all" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-undo" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-select-all" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-undo" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-find-and-replace@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-46.0.2.tgz#dbd32fd4f65e085f000631569911f83eb2d9502a" - integrity sha512-k/gAR69CxdjeBf7mrGKWswdsVrdXoHRjCR7RbnTJH+tgzPpbn1sZydD2UacqqC5hON088whTokDY3KFd6zdbXA== +"@ckeditor/ckeditor5-find-and-replace@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-46.0.3.tgz#c2b4b617ea0c5009d5bbf5366865c52ed7721eab" + integrity sha512-WKJ32slfJKPE2xnOWtk8/kqaDlUE3AKXChmRw6fPXM9pRpBRItLrbMO4Lhic9F1V8UzzY88/6VMuTMUlVg7/pQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-font@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-font/-/ckeditor5-font-46.0.2.tgz#874dd5102cc0c6e9152e9d27d4895806bfea644c" - integrity sha512-dKkjRE8+GU6+LtQP45nQSEJkvnW1xltdpHZQrZCKXlf/51b2gBg408JtSBhqc1NOT5t1ZxaJCKHnf91dd6g4Hg== +"@ckeditor/ckeditor5-font@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-font/-/ckeditor5-font-46.0.3.tgz#2d7e6d27f6cc0841029fca64224ebeebd46963f7" + integrity sha512-4A0F3ShSn5QE0aQVus45EiIpFntJdXQnlf/kCLbQstYBUof915vReCa/c0cRu8q+1GOB9DmTarSPfb2jxDKhaA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-fullscreen@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-fullscreen/-/ckeditor5-fullscreen-46.0.2.tgz#86813dfebb92a2ed6fbc8e266adeb3d3aa247f22" - integrity sha512-G+w2c5PpKRa9e5mZKR333FKkS1BH5bwKnkc0Xw4p2fowdIaytyv73fmUk2oQMTWEEe8sMMNfXCe69sfRSm4FmA== +"@ckeditor/ckeditor5-fullscreen@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-fullscreen/-/ckeditor5-fullscreen-46.0.3.tgz#aaca7671cd65864924a23ac25a41990d1a0d5f31" + integrity sha512-+AjKdmknSeihgVytx2CZPvqJ8Iv0sQd8kP1AvTMsp7JWr9kP3eMZEWJ3IwUP7GaH9O+cSDqeW2pFY4rW1ajYlQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-editor-classic" "46.0.2" - "@ckeditor/ckeditor5-editor-decoupled" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-editor-classic" "46.0.3" + "@ckeditor/ckeditor5-editor-decoupled" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-heading@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-46.0.2.tgz#fdaf00bfc56f792a66060c7df9c0455486e4a5dd" - integrity sha512-AdvE53zuBGyuiBitaLPztWL/OyT3hG9F2kcdf1yG+RYovLXS6lG2Ut1tEL3jzmTNOoObWLQQ9Jpthj7gawXlQw== +"@ckeditor/ckeditor5-heading@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-46.0.3.tgz#5d90467e9e4f082d8c8ec1dc3b31474b74e0c320" + integrity sha512-FKTgc1I9nDvnoDJ6RzkmPX7knhU3k6iH8IGUngH78TIOmhcWPVzv7Sftszos/LdX+kTc1ZoWWaHo5vrk90waZg== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-paragraph" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-paragraph" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-highlight@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-46.0.2.tgz#71a95009de63164babe51df704a3bbaa8cd15130" - integrity sha512-wOLa7exXWaIObdFmXIWchgfDEUyk4+j2/B25NLXyYFhk+EVDOIA0le48Tq+nAM7cusA6PP4skwkUZCBOP31UIA== +"@ckeditor/ckeditor5-highlight@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-46.0.3.tgz#c75991f017a039a500bec66e17e8a07ed8a44533" + integrity sha512-woO40tvOomrE7PHV/LAIOuNDb6sm2xiRQpT3r6TU1bvHZWSdt+hBCVRbnPxMNY2b/+0FGeV6cIOP8jlZ6JXF2g== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-horizontal-line@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-46.0.2.tgz#65a6ed55eeee429c4f80377c38bc02a15c2b2ac3" - integrity sha512-TWpcU7xDQnqyKvvv30cYHy+57FTLEuNgUbKRs+ziP1Ywogd6X3jFVnmJk/WMCNc315v1IfDFiuaPbZn04zrmjA== +"@ckeditor/ckeditor5-horizontal-line@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-46.0.3.tgz#c57556048fbb22221a347993e2ead695f05f730a" + integrity sha512-mct0XA6XxSk9BXorR5HA6jiDmf40Wm2HbwSEL8RcCQ4s/ak+3c85loUQZtV5Enaro8ejUkQ30nbqUnrO21Z8ZA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-html-embed@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-46.0.2.tgz#cafad91a0f935ce83262c189f7b425decc6b8a3c" - integrity sha512-GJouBoKYKEP1NYrMSeu+vadP5vHsJgUBb/9yvx+kup/50u+HOylenBfVc+IdMMzZyU8ZoNw3wND5mgOpyQPLdQ== +"@ckeditor/ckeditor5-html-embed@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-46.0.3.tgz#8153337107ea4ebd6cf98e8a67f57bcf5814272a" + integrity sha512-8Cf0L1REllrVffu4BrnNiga0mQgFcQ0V/L4ARMGR3vmafTvS2cOvMyrGJy/69oCGM0NigyU1eSzkGv04o+599w== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-html-support@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-46.0.2.tgz#c07a5de1f2307e716606a6b2e89e72c986f880bb" - integrity sha512-DZAMx55Qxz7YQMy4qOCiNKf9oUp/FkAxqJRAG+102nweLQePq86w//oE6pc/mRo3q6U3/za8NLz6JP4L2duztw== +"@ckeditor/ckeditor5-html-support@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-46.0.3.tgz#65164419632b679de09dd8040bf1d8ba837e7a51" + integrity sha512-zBRJ1aBIi/UKKRhCUvK0mTDu9c43GOINKscGJ4ZRAD8WmKdlpxO+xUfCfZouDMGwd67lD9e37LI3xZc+hGCXGA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-heading" "46.0.2" - "@ckeditor/ckeditor5-image" "46.0.2" - "@ckeditor/ckeditor5-list" "46.0.2" - "@ckeditor/ckeditor5-remove-format" "46.0.2" - "@ckeditor/ckeditor5-table" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-heading" "46.0.3" + "@ckeditor/ckeditor5-image" "46.0.3" + "@ckeditor/ckeditor5-list" "46.0.3" + "@ckeditor/ckeditor5-remove-format" "46.0.3" + "@ckeditor/ckeditor5-table" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-icons@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-icons/-/ckeditor5-icons-46.0.2.tgz#ef8994441f13c2d9bf33d8760e7093049d8ab0cc" - integrity sha512-QNLncoTeHgv4fU7Q/jv/qWH1nQMQ1JreWVQLysu1nEDlm4KiVLzP+8ng51BquY+wxw4rIVJTwZv1FYdyc6xlQw== +"@ckeditor/ckeditor5-icons@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-icons/-/ckeditor5-icons-46.0.3.tgz#fae5dec3826f5f4a6649fa01152d1aaa234a1d30" + integrity sha512-ztmFx8ujcdIMTWeIQ8Hxixlexfhx8vcclV/+maDzjVHhqRNi9eZ1b/nQ7gnS4/X5Fnh6cPQuCM+3lTUR4jQscA== -"@ckeditor/ckeditor5-image@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-image/-/ckeditor5-image-46.0.2.tgz#57d2d7e6118cf7eae5a535da0ffe9816d27fc2a3" - integrity sha512-1b72bijZ4lhysL6K9ZZBQZPldMUZwoAar4DFHmCnM/WN6psf/MEyFce+hr5Qq/LFOvCiOeevuNz6DTDKO7eXSg== +"@ckeditor/ckeditor5-image@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-image/-/ckeditor5-image-46.0.3.tgz#51814618fdb9ffe29217746cb28730dbf83911ab" + integrity sha512-9XcJVJxG+fqzwTupf7EATKeVZ+tXqeWiHLip4w/vMejjX026CPjiB3rKA2K5/H25TKDrvsMBBm22RqpK25dzCw== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-undo" "46.0.2" - "@ckeditor/ckeditor5-upload" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-undo" "46.0.3" + "@ckeditor/ckeditor5-upload" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-indent@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-46.0.2.tgz#b36f7ecaeec9be0ebdc2f2fef68fd4dc468a6034" - integrity sha512-EKA4kM3uZexI6j7GzQyDuYNwY0ULRet0+AZTYbr4rEaB+Mo2zaJCJxuJw1RPTNBwE/9fVJyqYsPzb0UmSRqsGQ== +"@ckeditor/ckeditor5-indent@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-46.0.3.tgz#ee6a0279c9a09d2a8be0b43d3fb3aa48ec074417" + integrity sha512-XLdlp94Bitkki027adnOqL642kCSJphMoZZDYYpTNHQkKhJq6TDp8u66EFlo2/q1quVDgb1qlezDuShouYd1tQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-heading" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-list" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-heading" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-list" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-language@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-language/-/ckeditor5-language-46.0.2.tgz#518488ca4195a62809ac8945540ac46291892743" - integrity sha512-eYwRnEkoWGabEZ4PVtSobORa+vnUQFuRetInuhDrkBwyMv9IjVUukS46AWHEjkPBO/rlI++O9SK1oOFyzOARCg== +"@ckeditor/ckeditor5-language@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-language/-/ckeditor5-language-46.0.3.tgz#dad8aa2fa391c247001f2812a603234992c74dfa" + integrity sha512-JLkDnhZxP9J/Dw7uxJtBHYrdR1q2xpkIsi+Y0fhG0cejo6Lhfnv2F/1L76EO6JxhfhrkHWrDgLwr860PYvRztA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-link@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-link/-/ckeditor5-link-46.0.2.tgz#8b4a6f5fe3cd3028534116b1fb8fb2f00ea42bd5" - integrity sha512-5uliK3QCIOcEsq2bgZF5Qz88cmN0E1YXUrYc5uoqC8LF0lzOimE+EA+7/dJhBZCya8/+Y/rvvpJ8SHsjhd++kg== +"@ckeditor/ckeditor5-link@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-link/-/ckeditor5-link-46.0.3.tgz#383c13c5bfa08c36f7305abc17e6129174806cc2" + integrity sha512-s2wBD0QQ2Pz8wzTbh3YN83QbYRVbGp3qLwgN+8x7Y/bOuFE4AxR+JhDo14ekdXelXYxIeGJAqG2Z4SQj8v2rXQ== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-image" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-image" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-list@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-list/-/ckeditor5-list-46.0.2.tgz#5eae9c0376e50eeb8b6bdb9c16614d71922a13c9" - integrity sha512-0Pq5UU4SP9UOlcRhxpjCoGXfDxHeqdumn8qtNbL5X5yRGqRE4GsVgJ4CkOmtZNTy1JVv1clZ37NPKh5miqTP4A== +"@ckeditor/ckeditor5-list@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-list/-/ckeditor5-list-46.0.3.tgz#342a50f272b7079a3c0bc863d70855721d4c44bc" + integrity sha512-KEAnyhUO6hWWa3GO6NGS7Entn2OXutCQ2+od8l5MrqeGxmpnqj0OpPX6qn+RZTVWf1RnqwErCYQhhPoQM/mlZg== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-font" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-font" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-markdown-gfm@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-46.0.2.tgz#a6e3312bbd7066f1d96e8f8a8f6eb8946c0f1542" - integrity sha512-+PaA5D10LnxqrsdW+UI45vqjR7C0l6vWAHFR+M99v7bxHEW+hQiLS6af8FhL/yv9Sno9AL4Oqdsee1HUU7hjHA== +"@ckeditor/ckeditor5-markdown-gfm@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-46.0.3.tgz#f3c17385d7e6489e525632bff3d59166c5e6cf94" + integrity sha512-ROOQsKcb03UdzyWZOD4p6vPWUpjgBRf4VXgbxKds2z19dm3fOdUwFbolpVrmYuYzdHrI/0xWM/+waD7TEOatuQ== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" "@types/hast" "3.0.4" - ckeditor5 "46.0.2" + ckeditor5 "46.0.3" hast-util-from-dom "5.0.1" hast-util-to-html "9.0.5" hast-util-to-mdast "10.1.2" @@ -1366,271 +1358,271 @@ unified "11.0.5" unist-util-visit "5.0.0" -"@ckeditor/ckeditor5-media-embed@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-46.0.2.tgz#4b700713621a02ff6abaa9e84fa1b36438be57ec" - integrity sha512-HQqtmuZPGvMKvshVIkz9GQvnSxuvsuw1o99zHvkr73H2OpL2uRRgCwVLufKZpIsn6CMtNbWq9PlZxk6ZME6Nyg== +"@ckeditor/ckeditor5-media-embed@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-46.0.3.tgz#5efb29e50888bae4b38a1fdb79572bad2bed930a" + integrity sha512-aozP4L8WQuPOHBA5qXTQnH3kQrhFJd6/J5KjKl5EicR6MUqeDkvzSLxYnltUBPByoDvkNxHD/GIL8nevgeWCrQ== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-undo" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-undo" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-mention@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-46.0.2.tgz#1ced5689dd81b3b3d9d5f64da895cd540c636aa6" - integrity sha512-/2FT0TmXyxgO5CWg841Yy5PF0uGT4mmp8NQYPpamfgP6E236L/aOTJP4kHtZV5uOSEnt6P48N59MTXswXA3Glg== +"@ckeditor/ckeditor5-mention@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-46.0.3.tgz#42f2e38b6404650f2d8a09392d8069832269ccf4" + integrity sha512-a7sHtN8M5Glh20SbsB0KWlFxoothUwkq6cqNJKKAI6MrOYsOJX1WaMG2mUfhGr4VTrUieuJYxVtqMFuagbhBgQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-minimap@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-46.0.2.tgz#0eee763bcf39b475db97abf09fcf66378e2a0342" - integrity sha512-Hi0qLjWLgGSwT1u3BlDc5tXMA5eHsDm6L9Sv+LiyxPFPBgX/HQhWT6L6x4jIexHQLlDhBO5o/Hp3tnlW57K5Kg== +"@ckeditor/ckeditor5-minimap@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-46.0.3.tgz#ba170968a44a87557319ea6efcf97eb3d8923e3a" + integrity sha512-gsac1z96MaJMFzapfzqLtEqETpI3JVXMfdQV3N0+kRbFSlUeJmrR/aHLC/+GDQAttkfOuL9i4FlWQKiDeSN15w== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-page-break@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-46.0.2.tgz#8a7303490fe884a69a3026852671b37a046d5f84" - integrity sha512-8wSzQU0lwoqzMPFyZHYVJJRTc1GA5gwgtz7XVKKHtKRF9FsKmHYASHsEsjjX3TkU0dPTGnaqsttZ7mBGU9K9Ww== +"@ckeditor/ckeditor5-page-break@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-46.0.3.tgz#c0fece6af88c11cddfc600e849ec04b11390c872" + integrity sha512-6V0O0sqgZMh47knEhhj0htWK3Oxm6jfHLWA4vi9vColwJMv9imuP72vYgrClmKHfN/QtyZ+DGmaufmhaXS2ffw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-paragraph@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-46.0.2.tgz#760280a3596a08466186021a60632c931821c2ba" - integrity sha512-Mg4BxYvIzonlLe9zzFZTyiiMbW40NLue9G26lWaCUz+O2z8ms5CShNc065t4alJiihJis5Dtuho8tvPDiRgCNg== +"@ckeditor/ckeditor5-paragraph@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-46.0.3.tgz#c6ee4808048c0c2a23450ab7438bc9dc5d140f4a" + integrity sha512-3OlCeyykkhcueXmo+p/LppeCvC2TtEpljLpC042EbIOCJEbSMlYEGx/AJQGetn2JV8q9L3UKfgnltpOriXAeyg== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" -"@ckeditor/ckeditor5-paste-from-office@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-46.0.2.tgz#26534e6b080f00c1305e293411ea674e60dfd07d" - integrity sha512-eI08nXazXzdIBxKjiU7tANFAdqz1cb5+xRdzn6dmZj0QBLHdEMWZVLLng5XC2gPqB7V3gSA0XbuYeSLF6fTfQg== +"@ckeditor/ckeditor5-paste-from-office@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-46.0.3.tgz#79de54d4cdec9531f254256d8e4d251aa02f6d38" + integrity sha512-pgqBTqP3oIFbmHvk1ddICDmyvBvFE9d+jO0busPXl5oWIqTLaaumwWaredEEUJpYmu02POSrK+WPGS0Qis6mdg== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-remove-format@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-46.0.2.tgz#804850404632c21ef63b3c75007309e5959e8c5c" - integrity sha512-/Ez72jjpnvDqFtP4afNimyrqbt3xJn/ab7p4DoByqyuBJ/Wy7mkaRcw9dDO0oJB+GVWdcGeRWeYoFUYj3Yw0NQ== +"@ckeditor/ckeditor5-remove-format@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-46.0.3.tgz#9f73003093a2958f32baffda6024f07b557f28ef" + integrity sha512-rrGeK1NGE5o04/wuyMq10BD7bJ7qkVZq74dDXb7G6l1IkFWU/lY5SLt1K4FgVunY+oBcsena+hktwqgEsmEqdg== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-restricted-editing@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-46.0.2.tgz#0e23d6aa6978ff9c554ab109975608b26518b0db" - integrity sha512-WR8HciP0DcD1TB+i8zRVwroPMiCy9Z7m0kfirCSLmwWP8bn792XwU+kId9DrOWalNzfNh4BXoviaPpi0vtRcmA== +"@ckeditor/ckeditor5-restricted-editing@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-46.0.3.tgz#52b32ac9c9ecfcfa12266e313b62936dcb75a1bc" + integrity sha512-b1NUb7nEKdb0R5UOukXRXOeweOIE3Dsa64uwV/H6ZnRfdOmH37TVSKFJ2lWVvPUUljsT3SVdSZbl1aP4aA1SBA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-select-all@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-46.0.2.tgz#d7c3ebcfe0a3e4f8e181f26a26cc21b48cfe1167" - integrity sha512-qC+HAZ0BWO4daXkZ84dAu7ynMRJfhtcnUP8pR/o2D6VxJO7Cu+5MwtwfoLmSiJAUGYwcxVd/iFq3RP7ZxS4Rew== +"@ckeditor/ckeditor5-select-all@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-46.0.3.tgz#a785a8cf89ddefb07e9cc9adc02844667bc02bd6" + integrity sha512-Uxr3/+TRLUIOGubXo/86yzqLGgoEdPV2rGqz40ulrVhG1Q7hOYerJPDs67ULPq6DLukoFFARRTah+UN9EOYRRw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" -"@ckeditor/ckeditor5-show-blocks@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-46.0.2.tgz#d3e0ffd6a9184519b711d0b469a53f3f552b5fdb" - integrity sha512-J+C59BMbnAH4gPrkUlu/dccKR2NBUqrRIFa01hnDHk+ECYeJsBNlsENNPImxeay4hiF+p4cujhQnI8Xq1NkzQQ== +"@ckeditor/ckeditor5-show-blocks@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-46.0.3.tgz#a912926c7102797426040a1bc36b73dcd380fbe5" + integrity sha512-YSa+Q49hQe4oRxIFsnUjzIFRG1M5+2vWjzYwS84hQAR0xDMZDD0SqIS6poC3QewuIS/525bcnmASBwXZUrRdIA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-source-editing@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-46.0.2.tgz#7164812ce5b24c92cde99e0ffb1bec171fd66c44" - integrity sha512-UdQELANPxAMhbbKTBCOfm/dMtqgQpMcU0D58LKjvvOT35ZGyjlrvZCKmXweFtfLPK5SmQhlS9z5/yy9JIH3pVQ== +"@ckeditor/ckeditor5-source-editing@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-46.0.3.tgz#ea664512ecd36ec5a32f5ee7f7bbd48e69e279c1" + integrity sha512-zJMa7ekyaeQAqAysFZDRwPRyJ7+ejaP2twYvRJQARf/BgZ6YZdSDvSoW1gGIKN/c/f0XWOSTDBdRCciPZu9vCg== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-theme-lark" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-theme-lark" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-special-characters@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-46.0.2.tgz#0b490d77bda1f8e1c8f3da87699c154cd4de1c40" - integrity sha512-X3XuIAchgFxmKcWcc513vzzsMcN6eOPOzQlQtVr9NKgUd/Zvw7YTyxCP1Wj2w9usgLn57p2ame/7GlBt/P1quw== +"@ckeditor/ckeditor5-special-characters@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-46.0.3.tgz#94b62a510608b47247c243fe12762fdfcdb1d4b4" + integrity sha512-PihS9/nmrGXaycsI3TSqVK0qGlc2ZSE3XzL7dEKTCyUta7vvI7hCC/jDaTtfch2d0fZhnIXovlgqlj35u2PjDw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" -"@ckeditor/ckeditor5-style@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-style/-/ckeditor5-style-46.0.2.tgz#23c964ede8de1715b942bc4399173100958f416c" - integrity sha512-LeP6kV0AeY1mrv6hbuQ2s10AEoJ64Vgv7XMAieg/fYE2/CIH0GAXE9/4Xt1+X8zCEddZ0HcbKCyCJG2l20xzyQ== +"@ckeditor/ckeditor5-style@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-style/-/ckeditor5-style-46.0.3.tgz#d1c75502c27cfd717a93f238b702432e48a5b02b" + integrity sha512-/4kOCM0/s4O65AA6tHdTK9joPFaTs/Uk14RHlyGP6+QJQ5FcNx9g2yJ1HxhRAdkMLy3AsVol9lqqFXC00+W7BA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-html-support" "46.0.2" - "@ckeditor/ckeditor5-list" "46.0.2" - "@ckeditor/ckeditor5-table" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-html-support" "46.0.3" + "@ckeditor/ckeditor5-list" "46.0.3" + "@ckeditor/ckeditor5-table" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-table@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-table/-/ckeditor5-table-46.0.2.tgz#a941a45394b3b8d5472861f98ce37dc2e67528b2" - integrity sha512-dGkTe1vEk7iDEmoRCTQszyerXvO5hrJH702kwHV5md2dlXyyJBteAJ9qHiSxf1euC2mOMMUhq7n5DlqpFAFb8A== +"@ckeditor/ckeditor5-table@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-table/-/ckeditor5-table-46.0.3.tgz#39bf048644c3fcc6a9747233b54803bcd86925fe" + integrity sha512-Bt7d02s96cv28Xc+LxNRYBNrqlG7gI5xB8gjQWCuoIYHVikxtDUSBowu7q1UOkBmX/TEHuUpnYjUdBKD5M2n5w== dependencies: - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-theme-lark@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-46.0.2.tgz#b87718cc592dbaf4063319ef7fd2b080df42dbe0" - integrity sha512-sHhwOZVg0e3SHm6caeHP67VlKojtoqxiu6oCwFduC+hK4s3OhQ3J/v+FIs7wGeFPz4ReBMAp63LNJVVcllRw+g== +"@ckeditor/ckeditor5-theme-lark@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-46.0.3.tgz#3707200acb4da4a8ba2a2afadb20a8b1bc0a9edb" + integrity sha512-0w4fwXFExlcsDsPXgNrQz86WJWCUwIYJkcRbjL+K3fMRYBPGVoBO25OHL7tPy2rYvrnZindCJXW9w8FzKSsKhA== dependencies: - "@ckeditor/ckeditor5-ui" "46.0.2" + "@ckeditor/ckeditor5-ui" "46.0.3" -"@ckeditor/ckeditor5-typing@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-46.0.2.tgz#4fb72c79f084ab96c297898857289823f3c7707b" - integrity sha512-jYrsRmE1rZ6c8jtOWVm6Q3FpIT9HWdJg6fK453w4upkjWM7lH3kXxtPgSLmEATUyO/ON91VNXEGA+LGml2MHnw== +"@ckeditor/ckeditor5-typing@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-46.0.3.tgz#449eb12d2916b8d6ffe026ee19a823cbeda1b460" + integrity sha512-iyxTTWIJ1/DpjCk+Uca9bE8P+Q7nvMssustEoMd6b3n39McCxnnonW7hrLUjFsRf/lPuvcAhpvFApoy2cbBRZA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-ui@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-46.0.2.tgz#d2bc6a2cbf8f557749ced031239db1f35a5f0867" - integrity sha512-c0Emy60YDY0EZl8nLPNaFoEA60cxQvfz8cD9uK7MYw9L5s4xSi+m0Nd0P2BR8gK/dfRnwiBnUyLDcu4yPMN1hw== +"@ckeditor/ckeditor5-ui@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-46.0.3.tgz#58d03f07402245ee92a9e6caad84f8a36e7770d4" + integrity sha512-5sRd7/IxWI+jL8N8CO5n35AwM5ofMieFLjvhtdzmkZsHl2hNHMHyfjERlOynp6tkX3TlelJBokqpAO7Yu+DrHA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-editor-multi-root" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-editor-multi-root" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" "@types/color-convert" "2.0.4" color-convert "3.1.0" color-parse "2.0.2" es-toolkit "1.39.5" vanilla-colorful "0.7.2" -"@ckeditor/ckeditor5-undo@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-46.0.2.tgz#94dddf6887e2d0b931aa739a52a015446b73a8d2" - integrity sha512-IOFL9rrYvk2KcNyFK9YPOENM3H7RRqtBNNmj9A9zntpqsoq+8QKqcY5BpcDeODrkOtmbrhwDwcwcek7uqI3S5g== +"@ckeditor/ckeditor5-undo@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-46.0.3.tgz#0aa086fb2df862451e525dd9f24bfd34410a2bfc" + integrity sha512-DnSBUIVOpARMDOtMrwvAOYAMZK263ubGLp48N4Yb4bpbE9VwH9KUaTNP1aRRE36wQ46KaPYiROqhnnq+RaemLQ== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" -"@ckeditor/ckeditor5-upload@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-46.0.2.tgz#3b1ab782e15b960df563daa4d5eb77a0ebb03df5" - integrity sha512-34lQ7Cx+/hiHAsY3yL+mwbD2Y1QPsqdr9VdgQU8McfwQNSh/PHBa5WplIMsdMRym8pEicj50nsli/hVl58FsZg== +"@ckeditor/ckeditor5-upload@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-46.0.3.tgz#8437e5d17db98a2c2e646f0b06e5c8941dba2b57" + integrity sha512-VfC3KG1fIaXQkzQRjIlt3b+G44DPj39jD9I5cepLN/xXsHU/EAUcJWXScsd/GlViSDR0DUDCygWyhIIbF/Vobw== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" -"@ckeditor/ckeditor5-utils@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-46.0.2.tgz#de4dff172d999b5cff5764092839f15e813ea03a" - integrity sha512-7t9PAZurES75Nz7ICadfRoGT5SbXnbxu6L5PoAxmyIGFPKICdZ6I4mVILVraPSNwgFDm/Zg2RxmiCOMWFTlxMg== +"@ckeditor/ckeditor5-utils@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-46.0.3.tgz#58831c99d3834b17146ea2a3d06d93fab932a1e2" + integrity sha512-z+4EI8IOSJpDzKdRSw0KHmLK3LMwYeZ9R207oQzswqlbvhYcUib3HhfMlwhE6pyAGYTofpZQ2btHEOaLPRCTDQ== dependencies: - "@ckeditor/ckeditor5-ui" "46.0.2" + "@ckeditor/ckeditor5-ui" "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-watchdog@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-46.0.2.tgz#ad14a7a2afe04d2e80f8ff294ade8951d1fd1815" - integrity sha512-QaXczfT5WgyteNVzbYWhZ0SBLQj/qXXRefMq0v1mpI9Iro44iMV7XmvOWhTVsskwTuNq32a1C5zMzfW0Ax69rQ== +"@ckeditor/ckeditor5-watchdog@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-46.0.3.tgz#78a6430ce6b9db0c8c0fcf5d5a2a539869ed7b29" + integrity sha512-TcSM3n9bsJ+Rpzc7NFN2BdobxXAnRJ52n0XY8CeVYZ0VA61GtG/zINH+OdEUORcpqKylH4F1ftyNEwf6cdUbPA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-editor-multi-root" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-editor-multi-root" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-widget@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-46.0.2.tgz#e84edc5f2bc477d31b6eb639949511656fba8647" - integrity sha512-uBcYwT7vTKCyuMXZIi0Qbs3neBQQp1sFFb/ClsX0elbh3UZEoVyr13uZIgl1+TrnVZa0scICJfWLbaiRHjVTXg== +"@ckeditor/ckeditor5-widget@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-46.0.3.tgz#4fda5f828f7a35e6d8b80b186d053b140cd1b5da" + integrity sha512-h5+KbQslzDVWntJQYCkSIj0huJSvE/lkjWTVCsbo2wmbKg6jusP+1oQ5ENtd7Nz4bpJlT83UkKDslSrF23xKlA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" es-toolkit "1.39.5" -"@ckeditor/ckeditor5-word-count@46.0.2": - version "46.0.2" - resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-46.0.2.tgz#5ad88e49ea96294dbd3dcc3ed9e19023c433dd49" - integrity sha512-U2b1DTchEE75ndHmDMmV3y/NXFFx9yIoSYzupsPJywKVTdBFdDZvSnulEocuP/YCgWTA1VWTiAirRTmccII/Qw== +"@ckeditor/ckeditor5-word-count@46.0.3": + version "46.0.3" + resolved "https://registry.yarnpkg.com/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-46.0.3.tgz#d0ffdb77e907f2eb913dade822a3b32b06194065" + integrity sha512-Qobva/b/79t4hD6ZgWsBT3PgGIFXU2dZW62kFDJNVkGpq1pkKboIdq7Iu57OffLDJaV+xkAmEvV6cIDWc4KADA== dependencies: - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - ckeditor5 "46.0.2" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + ckeditor5 "46.0.3" es-toolkit "1.39.5" "@csstools/selector-resolve-nested@^3.1.0": @@ -1838,9 +1830,9 @@ tslib "^2.8.0" "@fortawesome/fontawesome-free@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz#e4a6788a2d395ea97e7812a096e29bf9c95b944c" - integrity sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ== + version "7.0.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.1.tgz#c1ac7f07ba2df47d1de7b7236fad25c4e6ca5076" + integrity sha512-RLmb9U6H2rJDnGxEqXxzy7ANPrQz7WK2/eTjdZqyU9uRU5W+FkAec9uU5gTYzFBH7aoXIw2WTJSCJR4KPlReQw== "@gar/promisify@^1.0.1": version "1.1.3" @@ -1906,6 +1898,14 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" @@ -2076,9 +2076,9 @@ "@types/ms" "*" "@types/emscripten@^1.40.1": - version "1.40.1" - resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.40.1.tgz#4c34102d7cd1503979d4e6652082c23fd805805e" - integrity sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg== + version "1.41.1" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.41.1.tgz#318cc5f22c0108f62fe0ede8ef8c7aee38d6b43a" + integrity sha512-vW2aEgBUU1c2CB+qVMislA98amRVPszdALjqNCuUIJaEFZsNaFaM4g5IMXIs+6oHbmmb7q6zeXYubhtObJ9ZLg== "@types/eslint-scope@^3.7.7": version "3.7.7" @@ -2160,9 +2160,9 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" - integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== + version "24.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.1.tgz#b0a3fb2afed0ef98e8d7f06d46ef6349047709f3" + integrity sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g== dependencies: undici-types "~7.10.0" @@ -2393,7 +2393,7 @@ acorn-walk@^8.0.0: dependencies: acorn "^8.11.0" -acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0: +acorn@^8.0.4, acorn@^8.11.0, acorn@^8.15.0: version "8.15.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -2770,9 +2770,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001737: - version "1.0.30001737" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz#8292bb7591932ff09e9a765f12fdf5629a241ccc" - integrity sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw== + version "1.0.30001741" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz#67fb92953edc536442f3c9da74320774aa523143" + integrity sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw== ccount@^2.0.0: version "2.0.1" @@ -2849,72 +2849,72 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -ckeditor5@46.0.2, ckeditor5@^46.0.0: - version "46.0.2" - resolved "https://registry.yarnpkg.com/ckeditor5/-/ckeditor5-46.0.2.tgz#da85d11dc56a3cbac8599ae334f05854cedfbe8f" - integrity sha512-Ly+pG/OkF+9P7DaaaCp+VYJOm0+flxLR3Ue1thm10JnMvOW52XXYaRyoasAXoiGz6CC4lh0ZN7AtQSWu85oj3g== +ckeditor5@46.0.3, ckeditor5@^46.0.0: + version "46.0.3" + resolved "https://registry.yarnpkg.com/ckeditor5/-/ckeditor5-46.0.3.tgz#aa1f52ad6542e90aa4b720e592012c979e8b8194" + integrity sha512-BGadZ1td6emWnNVbX40nygpxZMAYQvtC/wRhdhedJpjqmwXQmwLte9Y9RZg+lnomrEiLiaxzFsz1j4I6u2fBnA== dependencies: - "@ckeditor/ckeditor5-adapter-ckfinder" "46.0.2" - "@ckeditor/ckeditor5-alignment" "46.0.2" - "@ckeditor/ckeditor5-autoformat" "46.0.2" - "@ckeditor/ckeditor5-autosave" "46.0.2" - "@ckeditor/ckeditor5-basic-styles" "46.0.2" - "@ckeditor/ckeditor5-block-quote" "46.0.2" - "@ckeditor/ckeditor5-bookmark" "46.0.2" - "@ckeditor/ckeditor5-ckbox" "46.0.2" - "@ckeditor/ckeditor5-ckfinder" "46.0.2" - "@ckeditor/ckeditor5-clipboard" "46.0.2" - "@ckeditor/ckeditor5-cloud-services" "46.0.2" - "@ckeditor/ckeditor5-code-block" "46.0.2" - "@ckeditor/ckeditor5-core" "46.0.2" - "@ckeditor/ckeditor5-easy-image" "46.0.2" - "@ckeditor/ckeditor5-editor-balloon" "46.0.2" - "@ckeditor/ckeditor5-editor-classic" "46.0.2" - "@ckeditor/ckeditor5-editor-decoupled" "46.0.2" - "@ckeditor/ckeditor5-editor-inline" "46.0.2" - "@ckeditor/ckeditor5-editor-multi-root" "46.0.2" - "@ckeditor/ckeditor5-emoji" "46.0.2" - "@ckeditor/ckeditor5-engine" "46.0.2" - "@ckeditor/ckeditor5-enter" "46.0.2" - "@ckeditor/ckeditor5-essentials" "46.0.2" - "@ckeditor/ckeditor5-find-and-replace" "46.0.2" - "@ckeditor/ckeditor5-font" "46.0.2" - "@ckeditor/ckeditor5-fullscreen" "46.0.2" - "@ckeditor/ckeditor5-heading" "46.0.2" - "@ckeditor/ckeditor5-highlight" "46.0.2" - "@ckeditor/ckeditor5-horizontal-line" "46.0.2" - "@ckeditor/ckeditor5-html-embed" "46.0.2" - "@ckeditor/ckeditor5-html-support" "46.0.2" - "@ckeditor/ckeditor5-icons" "46.0.2" - "@ckeditor/ckeditor5-image" "46.0.2" - "@ckeditor/ckeditor5-indent" "46.0.2" - "@ckeditor/ckeditor5-language" "46.0.2" - "@ckeditor/ckeditor5-link" "46.0.2" - "@ckeditor/ckeditor5-list" "46.0.2" - "@ckeditor/ckeditor5-markdown-gfm" "46.0.2" - "@ckeditor/ckeditor5-media-embed" "46.0.2" - "@ckeditor/ckeditor5-mention" "46.0.2" - "@ckeditor/ckeditor5-minimap" "46.0.2" - "@ckeditor/ckeditor5-page-break" "46.0.2" - "@ckeditor/ckeditor5-paragraph" "46.0.2" - "@ckeditor/ckeditor5-paste-from-office" "46.0.2" - "@ckeditor/ckeditor5-remove-format" "46.0.2" - "@ckeditor/ckeditor5-restricted-editing" "46.0.2" - "@ckeditor/ckeditor5-select-all" "46.0.2" - "@ckeditor/ckeditor5-show-blocks" "46.0.2" - "@ckeditor/ckeditor5-source-editing" "46.0.2" - "@ckeditor/ckeditor5-special-characters" "46.0.2" - "@ckeditor/ckeditor5-style" "46.0.2" - "@ckeditor/ckeditor5-table" "46.0.2" - "@ckeditor/ckeditor5-theme-lark" "46.0.2" - "@ckeditor/ckeditor5-typing" "46.0.2" - "@ckeditor/ckeditor5-ui" "46.0.2" - "@ckeditor/ckeditor5-undo" "46.0.2" - "@ckeditor/ckeditor5-upload" "46.0.2" - "@ckeditor/ckeditor5-utils" "46.0.2" - "@ckeditor/ckeditor5-watchdog" "46.0.2" - "@ckeditor/ckeditor5-widget" "46.0.2" - "@ckeditor/ckeditor5-word-count" "46.0.2" + "@ckeditor/ckeditor5-adapter-ckfinder" "46.0.3" + "@ckeditor/ckeditor5-alignment" "46.0.3" + "@ckeditor/ckeditor5-autoformat" "46.0.3" + "@ckeditor/ckeditor5-autosave" "46.0.3" + "@ckeditor/ckeditor5-basic-styles" "46.0.3" + "@ckeditor/ckeditor5-block-quote" "46.0.3" + "@ckeditor/ckeditor5-bookmark" "46.0.3" + "@ckeditor/ckeditor5-ckbox" "46.0.3" + "@ckeditor/ckeditor5-ckfinder" "46.0.3" + "@ckeditor/ckeditor5-clipboard" "46.0.3" + "@ckeditor/ckeditor5-cloud-services" "46.0.3" + "@ckeditor/ckeditor5-code-block" "46.0.3" + "@ckeditor/ckeditor5-core" "46.0.3" + "@ckeditor/ckeditor5-easy-image" "46.0.3" + "@ckeditor/ckeditor5-editor-balloon" "46.0.3" + "@ckeditor/ckeditor5-editor-classic" "46.0.3" + "@ckeditor/ckeditor5-editor-decoupled" "46.0.3" + "@ckeditor/ckeditor5-editor-inline" "46.0.3" + "@ckeditor/ckeditor5-editor-multi-root" "46.0.3" + "@ckeditor/ckeditor5-emoji" "46.0.3" + "@ckeditor/ckeditor5-engine" "46.0.3" + "@ckeditor/ckeditor5-enter" "46.0.3" + "@ckeditor/ckeditor5-essentials" "46.0.3" + "@ckeditor/ckeditor5-find-and-replace" "46.0.3" + "@ckeditor/ckeditor5-font" "46.0.3" + "@ckeditor/ckeditor5-fullscreen" "46.0.3" + "@ckeditor/ckeditor5-heading" "46.0.3" + "@ckeditor/ckeditor5-highlight" "46.0.3" + "@ckeditor/ckeditor5-horizontal-line" "46.0.3" + "@ckeditor/ckeditor5-html-embed" "46.0.3" + "@ckeditor/ckeditor5-html-support" "46.0.3" + "@ckeditor/ckeditor5-icons" "46.0.3" + "@ckeditor/ckeditor5-image" "46.0.3" + "@ckeditor/ckeditor5-indent" "46.0.3" + "@ckeditor/ckeditor5-language" "46.0.3" + "@ckeditor/ckeditor5-link" "46.0.3" + "@ckeditor/ckeditor5-list" "46.0.3" + "@ckeditor/ckeditor5-markdown-gfm" "46.0.3" + "@ckeditor/ckeditor5-media-embed" "46.0.3" + "@ckeditor/ckeditor5-mention" "46.0.3" + "@ckeditor/ckeditor5-minimap" "46.0.3" + "@ckeditor/ckeditor5-page-break" "46.0.3" + "@ckeditor/ckeditor5-paragraph" "46.0.3" + "@ckeditor/ckeditor5-paste-from-office" "46.0.3" + "@ckeditor/ckeditor5-remove-format" "46.0.3" + "@ckeditor/ckeditor5-restricted-editing" "46.0.3" + "@ckeditor/ckeditor5-select-all" "46.0.3" + "@ckeditor/ckeditor5-show-blocks" "46.0.3" + "@ckeditor/ckeditor5-source-editing" "46.0.3" + "@ckeditor/ckeditor5-special-characters" "46.0.3" + "@ckeditor/ckeditor5-style" "46.0.3" + "@ckeditor/ckeditor5-table" "46.0.3" + "@ckeditor/ckeditor5-theme-lark" "46.0.3" + "@ckeditor/ckeditor5-typing" "46.0.3" + "@ckeditor/ckeditor5-ui" "46.0.3" + "@ckeditor/ckeditor5-undo" "46.0.3" + "@ckeditor/ckeditor5-upload" "46.0.3" + "@ckeditor/ckeditor5-utils" "46.0.3" + "@ckeditor/ckeditor5-watchdog" "46.0.3" + "@ckeditor/ckeditor5-widget" "46.0.3" + "@ckeditor/ckeditor5-word-count" "46.0.3" clean-stack@^2.0.0: version "2.2.0" @@ -3656,9 +3656,9 @@ duplexer@^0.1.2: integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== electron-to-chromium@^1.5.211: - version "1.5.211" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz#749317bf9cf894c06f67980940cf8074e5eb08ca" - integrity sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw== + version "1.5.214" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz#f7bbdc0796124292d4b8a34a49e968c5e6430763" + integrity sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q== emoji-regex@^7.0.1: version "7.0.3" @@ -5728,9 +5728,9 @@ node-notifier@^9.0.0: which "^2.0.2" node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + version "2.0.20" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.20.tgz#e26bb79dbdd1e64a146df389c699014c611cbc27" + integrity sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -7390,12 +7390,12 @@ terser-webpack-plugin@^5.3.0, terser-webpack-plugin@^5.3.11: terser "^5.31.1" terser@^5.3.4, terser@^5.31.1: - version "5.43.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.43.1.tgz#88387f4f9794ff1a29e7ad61fb2932e25b4fdb6d" - integrity sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg== + version "5.44.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.0.tgz#ebefb8e5b8579d93111bfdfc39d2cf63879f4a82" + integrity sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w== dependencies: "@jridgewell/source-map" "^0.3.3" - acorn "^8.14.0" + acorn "^8.15.0" commander "^2.20.0" source-map-support "~0.5.20"